swift - 使用 UITableViewController 从 Firebase 获取当前子 ID

标签 swift firebase firebase-realtime-database

我的问题几乎和这里一样:Firebase get child ID swift ios与这个问题略有不同 Retrieve randomly generated child ID from Firebase .

我有一个 UITableViewController,用户可以在其中创建自己的 channel 。每个人都可以查看这些 channel ,每个人都可以点击其中一个 channel ,然后进入一个新的 View 。现在,当用户点击这些单元格中的任何一个时,我想获取用户刚刚点击的当前 channel ID。

我的数据库看起来像这样:channel database

现在我的问题是:如何获得当前 ID (-KdD6[...]Q0Q)?问题 2 的答案对房间的创建者有用,但不能被其他用户使用,因为他们没有创建 key 。可以使用问题 1 的答案,但截至目前,它只是循环遍历每个随 secret 钥。但我需要与用户当前所在 channel 相关的 key 。下面是一些代码,用于展示我目前如何创建 channel 。提前致谢。编辑:这是我所有的代码。我现在在 didSelectRow 函数中收到错误“[Channel] 类型的值没有成员‘name’”。

import UIKit
import Firebase
import Foundation

enum Section: Int {
    case createNewChannelSection = 0
    case currentChannelsSection
}

extension multiplayerChannelView: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        filterContentForSearchText(searchText: searchController.searchBar.text!)
    }
}

class multiplayerChannelView: UITableViewController {


    // MARK: Properties
    var channels: [Channel] = []
    let searchController = UISearchController(searchResultsController: nil)
    var filteredChannels = [Channel]()
    private lazy var channelRef: FIRDatabaseReference = FIRDatabase.database().reference().child("channels")
    private var channelRefHandle: FIRDatabaseHandle?
    var senderDisplayName: String?
    var newChannelTextField: UITextField?


    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Rooms"
        observeChannels()
        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        definesPresentationContext = true
        tableView.tableHeaderView = searchController.searchBar
    }
    deinit {
        if let refHandle = channelRefHandle {
            channelRef.removeObserver(withHandle: refHandle)
        }
    }
    func filterContentForSearchText(searchText: String, scope: String = "All") {
        filteredChannels = channels.filter { Channel in
            return (Channel.name.lowercased().range(of: searchText.lowercased()) != nil)
        }

        tableView.reloadData()
    }
    @IBAction func createChannel(_ sender: AnyObject) {
        if let name = newChannelTextField?.text {
            let newChannelRef = channelRef.childByAutoId()
            let channelItem = [ // 3
                "name": name
            ]
            newChannelRef.setValue(channelItem)
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
    private func observeChannels() {
        channelRefHandle = channelRef.observe(.childAdded, with: { (snapshot) -> Void in // 1
            let channelData = snapshot.value as! Dictionary<String, AnyObject> // 2
            let id = snapshot.key
            if let name = channelData["name"] as! String!, name.characters.count > 0 { // 3
                self.channels.append(Channel(id: id, name: name))
                self.tableView.reloadData()
            } else {
                print("Error! Could not decode channel data")
            }
        })
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let currentSection: Section = Section(rawValue: section) {
            switch currentSection {
            case .createNewChannelSection:
                return 1

            case .currentChannelsSection:
                if searchController.isActive && searchController.searchBar.text != "" {
                    return filteredChannels.count
                }
                else{
                return channels.count
                }
            }
        } else {
            return 0
        }
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let reuseIdentifier = (indexPath as NSIndexPath).section == Section.createNewChannelSection.rawValue ? "NewChannel" : "ExistingChannel"
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)

        if (indexPath as NSIndexPath).section == Section.createNewChannelSection.rawValue {
            if let createNewChannelCell = cell as? CreateChannelCell {
                newChannelTextField = createNewChannelCell.newChannelNameField
            }
        } else if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
            if searchController.isActive && searchController.searchBar.text != "" {
                cell.textLabel?.text = filteredChannels[(indexPath as NSIndexPath).row].name
            } else {
            cell.textLabel?.text = channels[(indexPath as NSIndexPath).row].name
        }
        }

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.section == Section.currentChannelsSection.rawValue {
            var channel = channels
            if searchController.isActive && searchController.searchBar.text != "" {
                channel = [filteredChannels[(indexPath as NSIndexPath).row]]
                channelRef.queryOrdered(byChild: "name").queryEqual(toValue: channel.name).observe(.childAdded, with: {snapshot in
                    let currentID = snapshot.key
                    print(currentID)
                })
            }
            else
            {
                channel = [channels[(indexPath as NSIndexPath).row]]
                channelRef.queryOrdered(byChild: "name").queryEqual(toValue: channel.name).observe(.childAdded, with: {snapshot in
                    let currentID = snapshot.key
                    print(currentID)
                })
            }

            self.performSegue(withIdentifier: "ShowChannel", sender: channel)
        }
    }

}

    internal class Channel {
      internal let id: String
      internal let name: String

      init(id: String, name: String) {
        self.id = id
        self.name = name
      }
    }

编辑 2:我阅读了关于 prepare for segue 的内容,但是这不会正确执行:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)
        if let channel = sender as? Channel {
        let chatVc = segue.destination as! channelMultiplayerViewController
        chatVc.channel = channel
        chatVc.channelRef = channelRef.child(channel.id)
    }
}

执行 prepare for segue 函数,但是 if let channel = sender as? Channel 不是,因为它是 nil。

最佳答案

您必须向 firebase 查询 key 。当前设置的唯一问题是您希望防止重复名称。这样做的原因是,如果您没有在应用程序中存储每个 channel 的所有 key 并且必须查询 key ,则查询确切 key 的唯一方法是通过单独的节点“名称”。如果存在重复名称,那么我添加到您的 did select row 方法的代码中的 childAdded 将返回两个,并且没有其他数据可以帮助识别它。

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if indexPath.section == Section.currentChannelsSection.rawValue {
        var channel = channels
        if searchController.isActive && searchController.searchBar.text != "" {
            channel = [filteredChannels[(indexPath as NSIndexPath).row]]
            channelRef.queryOrdered(byChild: "name").queryEqual(toValue: channel.name).observe(.childAdded, with: {snapshot in
                let currentID = snapshot.key
            })
        }
        else
        {
            channel = [channels[(indexPath as NSIndexPath).row]]
            channelRef.queryOrdered(byChild: "name").queryEqual(toValue: channel.name).observe(.childAdded, with: {snapshot in
                let currentID = snapshot.key
            })
        }
        self.performSegue(withIdentifier: "ShowChannel", sender: channel)
    }
}

我建议您获取已加载的所有 channel 的数据:

override func viewDidLoad(){
    channelRef.observe(.childAdded, with: {snapshot in
        let data = snapshot.value as? [String:Any] ?? [:]
        let key = snapshot.key
        let name = data["name"]
        let chnl = Channel.init(id:key, name:name)
        self.channels.append(chnl)
        self.tableView.reloadData()
    })
}

当您使用 .childAdded 时,此 VC 的监听器将处于事件状态,因此无论您何时创建新 channel ,都会调用此代码并重新加载您的表格。

否则您可以在创建 channel 时直接获取他们的 key :

@IBAction func createChannel(_ sender: AnyObject) {
if let name = newChannelTextField?.text {
    let newChannelRef = channelRef.childByAutoId()
    let channelItem = [
        "name": name
    ]
    newChannelRef.setValue(channelItem)
    let channelKey = newChannelRef.key
    let newChannel = Channel.init(id:channelKey, name:name)
    self.channels.append(newChannel)
    self.tableView.reloadData()
}

请注意,如果您走这条路线,则不会有任何新添加的项目的活跃监听器,这意味着如果另一台设备正在添加 channel ,那么其他设备将不会获得更新。最好通过查询从 firebase 中获取新创建的数据,并在将数据添加到数组时重新加载表。

这是一个适用于上述情况的示例 Channel 类:

import Foundation
// import FirebaseDatabase // import this if you would like to use this class to handle data transactions

class Channel: NSObject {
    // change either of these to var if they should be mutable
    let id:String
    let name:String 
    // initialize
    init (_ id:String, name:String){
        self.id = id
        self.name = name
    }
}

这样您就有了一个可以使用的模型对象。如果您不熟悉 MVC 编码风格,您应该检查一下。它使应用程序更加高效。您可以采用此 Channel 模型并添加数据检索编码,以便此类为您的 Controller 处理数据。不过,这远远超出了您帖子的范围。现在这个类应该工作。

根据最后一条评论编辑: 在 channel 选择期间在两个查询 block 中添加您的 prepareforsegue 方法,而不是在 if block 之后,因为它可能会在查询完成之前执行 prepare for segue。不过要注意一件事,在我意识到这一点之前,我曾经和你做过同样的事情;是您此时不必运行查询来获取所选单元格的数据,因为它在您的本地数组中。嗯,目前你只听添加的 child 。您还可以监听已删除和更改的子项,以便始终更新您的本地数据,然后当您选择单元格时,您可以直接访问 filteredChannels/channels 数组并通过 prepareforsegue 方法推送 channel 。我开始这样做是因为在表选择期间查询总是会导致奇怪的错误,我必须找到创造性的方法。

childRemoved 和 childChanged 查询可以像您当前的 .childAdded 方法一样实现,因为您希望保留监听器的处理程序以在页面取消时删除它们。但是,如果您不允许删除或更新 channel ,则可以忽略它。

关于swift - 使用 UITableViewController 从 Firebase 获取当前子 ID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42308435/

相关文章:

swift - 属性 getter 和 setter

ios - 为什么标签栏项目不显示?

java - 更改 Firebase 数据结构后出现 UnrecognizedPropertyException

Firebase Pub/Sub 触发器 : executing multiple times sporadically

android - Firebase 检索/读取返回空值 - Android

java - Datasnapshot HashMap 不能转换为另一个类

swift - 无法实例化 View Controller Swift

ios - 图像不适合屏幕

android - 像地理围栏一样使用 geofire

ios - "Firebase_VERSION is not defined: add -DFirebase_VERSION=... to the build invocation"