将数据从两个单独的 Firebase 节点加载到一个表格视图单元格中

Posted

技术标签:

【中文标题】将数据从两个单独的 Firebase 节点加载到一个表格视图单元格中【英文标题】:Loading Data From Two Seperate Firebase Nodes Into One Table View Cell 【发布时间】:2017-01-28 12:37:38 【问题描述】:

我是 Swift 3 和 Firebase 的新手,因此在从两个不同的节点检索数据并将它们显示在一个表格视图单元格中时遇到了一些问题。我在 Stack Overflow 上发现了一些其他类似的问题,如下所示:

Firebase - ios Swift: load table view cell with data retrieved from two separate child nodes

但无法将其应用到我的代码中,因为这些示例对于询问的人来说过于具体,或者我对 Firebase 缺乏了解,导致我无法继续提供所提供的答案。

我的应用程序是使用 Xcode-8 用 Swift 3 编写的,并使用 Firebase 进行数据持久化。该应用程序的目的是允许用户提交不同的锻炼计划以供其他用户使用。用户提交的程序有与他们相关联的作者的 uid,我打算使用它然后根据这个 uid 值从一个单独的节点检索用户的用户名。

我的 Firebase 设置:

    "programs" : 
    "-KYF3o3YD6F3FEXutuYH" : 
      "content" : "Program Content Goes Here...",
      "duration" : "4 Weeks",
      "title" : "Chest Blast",
      "type" : "Hypertrophy",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    ,
    "-KYF4ev88FQ2nEr6yTOW" : 
      "content" : "Program Content Goes Here...",
      "duration" : "6 Weeks",
      "title" : "Full Back Workout",
      "type" : "Strength",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    ,
    "-KZRYF9A8-8OHCNzOoPT" : 
      "content" : "Eugene and Eamon",
      "duration" : "4 Weeks",
      "title" : "abc",
      "type" : "abc",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    ,
    "-KbKNdrAcBarpaNoGf_e" : 
      "content" : "Test",
      "duration" : "test",
      "title" : "New Test",
      "type" : "test",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    ,
    "-KbKnXnyzj_EJp_wNw5y" : 
      "content" : "1. Barbell Bench Press\n\nWhy it's on the list: You can generate the most power with barbell lifts, so the standard barbell bench allows you to move the most weight. It's also an easier lift to control than pressing with heavy dumbbells. The exercise is easy to spot and relatively easy to learn (if not master), There are plenty of bench-press programs you can follow to increase your strength.\n\n1. Barbell Bench Press\n\nWhy it's on the list: You can generate the most power with barbell lifts, so the standard barbell bench allows you to move the most weight. It's also an easier lift to control than pressing with heavy dumbbells. The exercise is easy to spot and relatively easy to learn (if not master), There are plenty of bench-press programs you can follow to increase your strength.",
      "duration" : "1",
      "title" : "1",
      "type" : "1",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    
  ,
  "users" : 
    "hds1WketiAUELVDOz1Dprvlu0KE3" : 
      "username" : "Example"
    ,
    "oLy9GOzDyKht7WWVZgpd3jPHxsE3" : 
      "username" : "Test"
    
  

应用程序中的表格如下所示:

如您所见,此时作者仅由其 uid 指示。这很容易,因为我将它存储在与显示的所有其他数据相同的位置,但我需要使用该 uid 值来搜索另一个节点并获取与其关联的用户名并将其显示在现在显示 uid 的位置。

下面是我用来检索和显示数据的代码。我的主要问题是:

1:我可以使用什么查询来搜索用户节点以查找匹配的 uid,并仅获取该人的用户名并将其显示在表格视图中?

2:我应该将该查询放在代码中的什么位置?我想把它放在 func tableView() 方法中,因为每次将新帖子添加到单元格时我都可以搜索用户,这样我就不必制作另一个 NSMutable 数组来保存可能甚至没有帖子。非常感谢您提供的任何帮助。

@IBOutlet weak var programsTableView: UITableView!
var programs = NSMutableArray()

override func viewDidLoad() 
    super.viewDidLoad()

    self.programsTableView.delegate = self
    self.programsTableView.dataSource = self

    //Call load data method to populate table with data from Firebase
    loadData()


//Method to load data from Firebase
func loadData()

FIRDatabase.database().reference().child("programs").observeSingleEvent(of: .value, with:  (snapshot) in
        //Snapshot holds value and it is casted to NS Dictionary
        if let programsDictionary = snapshot.value as? [String: AnyObject]
            for program in programsDictionary
                self.programs.add(program.value)
            
            self.programsTableView.reloadData()
        
    )

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! AllProgramsTableViewCell

    // Configure the cell...
    let program = self.programs[indexPath.row] as! [String: AnyObject]

    //Populate row
    //Grab title and add it to cell
    cell.titleLabel.text = program["title"] as? String
    //Grab type and add it to cell
    cell.typeLabel.text = program["type"] as? String
    //Grab duration and add it to cell
    cell.durationLabel.text = program["duration"] as? String
    //Grab content and add it to cell
    cell.contentTextView.text = program["content"] as? String
    //Grab author and add it to cell
    cell.authorLabel.text = program["uid"] as? String

    return cell

【问题讨论】:

请删除您的 Firebase 结构的图片,并将实际的 Firebase 结构发布为文本,请不要使用图片。这可以从您的 Firebase 控制台->右侧的三个点->导出 JSON 中获得。使用文本很重要,因为它是可搜索的,并且有助于我们为您提供帮助,因为我们不必在答案中重新输入。 @Jay 我做了这些改变。道歉。 【参考方案1】:

一些事情可以帮助你。

1:我可以使用什么查询来搜索用户节点以查找匹配的 uid 并仅获取该人的用户名并将其显示在表格中 查看?

首先,更改您的用户节点并利用 uid 作为每个用户的键

users
  uid_0
    name: "Hans"
    userName: "pump_u_up"
  uid_1
    name: "Franz"
    userName: "abs_of_steel"

这避免了查询。查询比观察“重”并且占用更多资源。通过使用uid,您可以直接捕获用户信息。例如:

let userRef = rootRef.child("uid from programs node")
userRef.observeSingleEvent(of: .value, with:  snapshot in
    let userDict = snapshot.value as! [String:AnyObject]
    let userName = userDict["userName"] as! String
    print(userName)
)

2:我应该将该查询放在代码中的什么位置?我想只是 把它放在 func tableView() 方法中,因为我可以搜索 每次将新帖子添加到单元格时的用户,这样我 不必制作另一个 NSMutable 数组来保存可能 甚至没有帖子。非常感谢您提供的任何帮助。

现在您已经有了捕获用户信息的代码,添加它是一件轻而易举的事,但是,我建议采用与发布内容略有不同的方法。

从 ProgramClass 开始

class ProgramClass 
    var key = ""
    var content = ""
    var duration = ""
    var username = ""

并因此填充数据源:

ref.child("programs").observeSingleEvent(of: .value, with:  (snapshot) in
     for snap in snapshot.children 
          let programSnap = snap as! FIRDataSnapshot
          let programKey = programSnap.key //the key of each program
          let programDict = programSnap.value as! [String:AnyObject] //program child data
          var aProgram = ProgramClass()
          aProgram.key = programKey
          aProgram.content = programDict["content"] as! String
          //etc
          let uid = programDict["uid"] as! String
          let userRef = ref.child("users").child(uid)
          userRef.observeSingleEvent(of: .value, with:  snapshot in
             let userDict = snapshot.value as! [String:AnyObject]
             let userName = userDict["userName"] as! String
             aProgram.username = userName
             self.programsArray.append[aProgram]
        )
    

上面的代码可以大大缩短,但为了便于阅读,它会更加冗长。

哦,别忘了以 Swifty 的方式定义你的数据源数组:

var programsArray = [ProgramClass]()

【讨论】:

我喜欢数据类的想法(这里也可以使用轻量级结构) @Jay 感谢您抽出宝贵时间发布答案 Jay。不幸的是,我对 Swift 和 Firebase 都是新手,所以你的回答有点过头了。我已经对用户节点进行了更改。 uid 现在是键,其子项是用户名。为了让这项工作在基本层面上工作,您是否可以提供和解释查询或“观察”以解析用户节点,并从程序中获取与 uid 匹配的人的用户名并将其添加到表视图。虽然即使将它放在 tableView 函数中也不是最佳实践! 嗨,Jay,我修改了您的 let userRef = rootRef.child("uid from programs node") userRef.observeSingleEvent(of: .value, with: snapshot in let userDict = snapshot.value as! [String:AnyObject] let userName = userDict["userName"] as! String print(userName) ) 代码,似乎已经显示了用户名。我现在必须以这种新方式将用户名添加到数据库中,因为我刚刚使用控制台进行了更改,但这是一个很好的开始,谢谢。 @ACJ 太好了!如果有帮助,请采纳答案。【参考方案2】:

更新: getUserName 不能这样工作,因为 observeSingleEvent 是一个异步调用。 (感谢@Aodh)

PS:我不知道是删除答案还是留下一个坏例子更好?


解决方案是再次检索用户数据:

您应该更改您的架构,即您不在程序中保存 uid,而是在 userId 中保存

"programs":
     "programId1234":
         [...]
         "userId": "userid12345
"users":
     "userId1234":
         "uid": "uidxxxxxx"
         "username" : "jim"

代码:

FIRDatabase.database().reference().child("programs").observeSingleEvent(of: .value, with:  (snapshot) in
        //Snapshot holds value and it is casted to NS Dictionary

        //change to var!
        if var programsDictionary = snapshot.value as? [String: AnyObject]
            for program in programsDictionary
                // add there
                let programDictionary = program.value as? [String: AnyObject]
                let authorUid = programDictionary["userId"] as? String
                let authorName = getUserName(forUid: authorUid)
                programDictionary["authorname"] = autorName
                //
                self.programs.add(programDictionary)
            
            self.programsTableView.reloadData()
        
    )


func getUserName(forUid userID: String) -> String 
    var userName = nil
    FIRDatabase.database().reference().child("users").child(userID)
         .observeSingleEvent(of: .value, with:  (snapshot) in
              if let userDictionary = snapshot.value as? [String: AnyObject]
                  userName = userDictionary["username"] as? String
              
        )
    return userName


以后不需要再转换了——你可以改变这一行:

// Configure the cell...
let program = self.programs[indexPath.row] as! [String: AnyObject]

// Configure the cell...
let program = self.programs[indexPath.row]

这是join 调用的示例:https://firebase.googleblog.com/2013/10/queries-part-1-common-sql-queries.html#join

【讨论】:

我喜欢这个答案,但有很多编码问题。你能修复和更新吗?即让 authorUid = program["userId"] as?字符串不起作用,因为程序没有下标成员并且 var userName = nil 不是有效的语句(Swift3) 另外,为什么只是用 self.programs.add(program.value) 将值添加到数组中 program.key 是该节点的键 - 和 program.value 所有子节点。在你看到的问题中,在 cellForRow 中总是转换 @muescha 感谢您抽出宝贵时间发布您的答案,我设法在 Jay 的回答中取得了一些进展,所以我想我可以继续使用我的应用程序。再次感谢您。 @Aodh - 没错,有一个错误 - 它不是那样工作的。我会更新我的答案

以上是关于将数据从两个单独的 Firebase 节点加载到一个表格视图单元格中的主要内容,如果未能解决你的问题,请参考以下文章

Firebase - iOS Swift:使用从两个单独的子节点检索到的数据加载表格视图单元格

将Android Firebase数据库的子节点检索到单独的变量中?

Firebase Query 从两个节点一起获取数据

如何初始加载 UITableView 然后观察 Firebase 节点以更改在 Swift 中刷新 tableview?

在 Firebase 中保存数组

如何以正确的方式从 firebase 数据库加载图像