使用 UITableViewController 从 Firebase 获取当前子 ID

Posted

技术标签:

【中文标题】使用 UITableViewController 从 Firebase 获取当前子 ID【英文标题】:Get current child ID from Firebase using UITableViewController 【发布时间】:2017-02-17 22:26:32 【问题描述】:

我的问题与这里几乎相同:Firebase get child ID swift ios,与此问题 Retrieve randomly generated child ID from Firebase 略有不同。

我有一个 UITableViewController,用户可以在那里创建自己的频道。每个人都可以查看这些频道,每个人都可以点击其中一个频道并进入新的视图。现在,当用户点击这些单元格中的任何一个时,我想获取当前频道 ID,用户刚刚点击了。

我的数据库如下所示:

现在我的问题是:如何获取当前 ID (-KdD6[...]Q0Q)?问题 2 的答案对房间的创建者有用,但不能被其他用户使用,因为他们没有创建密钥。可以使用第 1 个问题的答案,但到目前为止,它只是循环遍历每个随机键。但我需要与用户当前所在频道相关的密钥。下面是一些代码,显示我现在如何制作频道。提前致谢。编辑:这是我所有的代码。我现在在 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:我读到了为 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。

【问题讨论】:

显示你在频道中添加对象的代码如果你设置了频道对象的名称,你是否设置了频道对象的 id 属性,如果你已经设置了,那么你想将此频道 ID 传递给你的新屏幕 ShowChannel? 我添加了更多代码,现在您可以准确地看到我在做什么。我只想检索房间的 ID,不仅为创建者,还为想要加入该房间的人。 【参考方案1】:

您必须向 firebase 查询密钥。当前设置的唯一问题是您希望防止重复名称。这样做的原因是,如果您没有在应用程序中存储每个通道的所有密钥并且必须查询密钥,那么查询确切密钥的唯一方法是通过单独的节点“名称”。如果存在重复名称,那么我添加到您的选择行方法的代码中的 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)
    

我建议您获取已加载的所有观看频道的数据:

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 将激活一个侦听器,因此无论何时您创建新频道,都会调用此代码并重新加载您的表。

否则你可以在创建频道时直接获取它们的密钥:

@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()

请注意,如果您走这条路线,则添加的任何新项目都没有活动的侦听器,这意味着如果另一个设备正在添加频道,那么其他设备将不会获得更新。最好通过查询从 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 模型并添加数据检索编码,以便此类处理您的控制器的数据。不过,这远远超出了您的帖子范围。现在这个类应该可以工作。

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

childRemoved 和 childChanged 查询可以与您当前的 .childAdded 方法实现相同,因为您希望保留侦听器的处理程序以在页面 deinit 上删除它们。但是,如果您不允许频道删除或更新,则可以忽略这一点。

【讨论】:

感谢您抽出宝贵时间回答这个问题。当我尝试将您的第一个代码块添加到我的项目时,我收到错误“[Channel] 类型的值没有成员'名称'”。你知道如何解决这个错误吗? Channel 类是同一个 swift 文件的一部分吗?最好创建一个名为 Channel.swift 的新 swift 文件作为 NSObject 的子类。我会用一个对你有用的例子来更新我的答案。 是的,内部类已经是一个单独的文件,但如果我没有这么说,对不起。我将您的代码复制到我的 Channel.swift 文件中,但我再次遇到相同的错误... 它只是将通道视为一个数组,没有 name 或 id 属性。我尝试了很多,但没有任何效果:(我添加了一个从其他地方获得的准备 segue 函数,但该函数无法正确执行......我再次更新了问题。 您的 perform segue 方法可能在查询返回数据之前被调用。查看我更新的答案,添加为评论太长了。

以上是关于使用 UITableViewController 从 Firebase 获取当前子 ID的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能使用“List”作为 UITableViewController 子类的类名?

在 UITableViewController 上使用 UIViewController 类

使用带有小型表格的 UITableViewController?

重新加载 UITableViewController

在 UITableViewController 中使用分页

Swift - 使用 UITableViewController 创建子菜单