【问题标题】:Table view continues to add rows while observing .childAdded even when child was not added in Firebase表视图在观察 .childAdded 时继续添加行,即使在 Firebase 中未添加子项也是如此
【发布时间】:2018-02-22 21:54:13
【问题描述】:

我有一个 UITableView,它由以下 firebase 数据库填充:

"games" : {
    "user1" : {
      "game1" : {
        "currentGame" : "yes",
        "gameType" : "Solo",
        "opponent" : "Computer"
      }
    }
  }

我在viewDidLoad 中加载了所有游戏,用户可以在另一个UIViewController 中创建新游戏,一旦用户这样做并导航回UITableView 我想用新行更新表格。我正在尝试使用以下代码来做到这一点:

var firstTimeLoad : Bool = true
override func viewDidLoad() {
    super.viewDidLoad()
    if let currentUserID = Auth.auth().currentUser?.uid {
        let gamesRef = 
        Database.database().reference().child("games").child(currentUserID) 

        gamesRef.observe(.childAdded, with: { (snapshot) in
            let game = snapshot
            self.games.append(game)
            self.tableView.reloadData()
        })
    }
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if firstTimeLoad {
        firstTimeLoad = false
    } else {
        if let currentUserID = Auth.auth().currentUser?.uid {
            let gamesRef = Database.database().reference().child("games").child(currentUserID)
            gamesRef.observe(.childAdded, with: { (snapshot) in
                self.games.append(snapshot)
                self.tableView.reloadData()
            })
        }
    }
}

假设数据库中有一个当前游戏,当viewDidLoad 运行时,表格正确显示为一行。但是,每当我导航到另一个视图并返回时,viewDidAppear 就会运行,并且由于某种原因,即使没有添加子项,games 似乎也会附加一个重复的game

games 数组正在填充单元格:

internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: 
IndexPath) -> UITableViewCell {

    let cell = Bundle.main.loadNibNamed("GameTableViewCell", owner: 
    self, options: nil)?.first as! GameTableViewCell

    let game = games[indexPath.row]
    if let gameDict = game.value as? NSDictionary {
        cell.OpponentName.text = gameDict["opponent"] as? String
    }

    return cell
}

更新

感谢大家的回答!似乎我误解了 firebase .childAdded 的运作方式,感谢您所有试图帮助我的答案

【问题讨论】:

  • 当你附加 self.games 数组时,你必须检查 self.games 数组是否包含与响应相同的对象。如果是,那么您应该避免追加,否则继续追加。然后执行 tableview.reloadData()。
  • 但是为什么 .childAdded 如果没有添加子元素会返回一些东西?
  • 我认为您在 viewDIdLoad 和 viewWillAppear 中编写了相同的逻辑,因此两者都会同时调用,并且即使响应对象相同,两个响应也会附加到同一个数组 self.games 中。
  • 我只在初始加载后运行 viewWillAppear,这样它们就不会同时运行。这就是我在那里的布尔检查。
  • 抱歉,我忘记在我的问题中包含 firstTimeLoad 的初始化,但它最初设置为 true,因此第一个 viewWillAppear 不会运行。

标签: ios firebase swift3 firebase-realtime-database


【解决方案1】:

据我所知,这里的问题是每次你推动视图控制器并返回到前一个时,它都会创建一个新的观察者,最终你会同时运行许多观察者,这就是为什么您的数据似乎重复。

你需要做的是在你的 viewDidDisappear 方法中,像这样向你的 gameRef 添加一个 removeAllObservers :

override func viewDidDisappear(_ animated: Bool) {
   super.viewDidDisappear(animated)

   guard let currentUserId = Auth.auth().currentUser?.uid else {
      return
   }

   let gamesRef = Database.database().reference().child("games").child(currentUserId)

   gamesRef.removeAllObservers()

}

我在这里看不到你所有的代码,所以我不确定发生了什么,但在添加你的孩子添加的观察者之前,你需要从你的数组中删除所有元素,如下所示:

games.removeAll()

实际上,根据最佳实践,您不应该在 ViewDidLoad 中调用您的方法,而是应该在 viewWillAppear 方法中添加您的观察者。

我现在无法测试您的代码,但希望它应该可以这样工作! 如果没有,请告诉我:)

更新:

如果你想首先加载所有数据,然后只拉取即将到来的新数据,你可以像这样使用observeSingleEvent(of: .value) 和observe(.childAdded) 观察者的组合:

var didFirstLoad = false

gamesRef.child(currentUserId).observe(.childAdded, with: { (snapshot) in

    if didFirstLoad {
       // add your object to the games array here
    }

}

gamesRef.child(currentUserId).observeSingleEvent(of: .value, with: { (snapshot) in

    // add the initial data to your games array here

    didFirstLoad = true
}

这样做,第一次加载数据时,.childAdded 将不会被调用,因为那时 didFirstLoad 将被设置为 false。只有在 .observeSingleEvent 被调用后才会调用它,从本质上讲,它只被调用一次。

【讨论】:

  • 我在 ViewDidLoad 中有一个观察者的原因是,最初所有数据都会被加载到表中。我希望每次用户在 viewWillAppear 中添加新游戏时更新表格。我想我可以让它每次都在 viewWillAppear 中获取数据,只是它每次都必须获取所有数据。
  • 哦,我明白了!我编辑了我的答案。那么在这种情况下,在添加观察者之前尝试从数组中删除所有元素,并让我知道它是否有效:)
  • 另外,如果您想保留在第一次加载期间加载所有数据的好处,而不是调用两个观察者(一个在 viewDidAppear 中,一个在 viewDidLoad 中),您可以添加一个 if else viewDidAppear 观察者中的语句来进行检查,因为 childAdded 将为每个初始孩子触发一次,每次添加新孩子时都会触发一次
  • 我不确定我是否理解正确,但似乎 .childAdded 仍然会提取所有数据,所以我真的没有像我想要的那样提高效率这不是 .childAdded 函数的方式。对吗?
  • 第一次会触发每个初始子项第一次是的。在那种情况下,我会首先使用observeSingleEvent观察者,我为你更新了我的答案。
【解决方案2】:

尝试以下代码,无需检查 bool ,避免在此处使用 bool 它的所有异步方法,当它的数据库增长时,它在我的聊天应用程序之间造成了一个问题

//Remove ref in didLoad
//Remove datasource and delegate from your storyboard and assign it in code so tableView donates for data till your array don't contain any data
//create a global ref 
let gamesRef = Database.database().reference()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

        self.games.removeAllObjects()
        if let currentUserID = Auth.auth().currentUser?.uid {
            gamesRef.child("games").child(currentUserID)observe(.childAdded, with: { (snapshot) in
                self.games.append(snapshot)
                self.tableView.dataSource = self
                self.tableView.delegate = self
                self.tableView.reloadData()
        })
            gamesRef.removeAllObserver() //will remove ref in disappear itself 
            //or you can use this linen DidDisappear as per requirement 
        }
        else{
           //Control if data not found
            }
        }

//TableView Delegate

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

            if self.games.count == 0{
                let emptyLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height))
                emptyLabel.text = "No Data found yet"
                emptyLabel.textAlignment = .center
                self.tableView.backgroundView = emptyLabel
                self.tableView.separatorStyle = .none
                return 0
            }
            else{
                return self.games.count
            }
        }

【讨论】:

  • 我不确定我是否理解正确,但似乎 .childAdded 仍然会提取所有数据,所以我真的没有像我想要的那样提高效率这不是 .childAdded 函数的方式。对吗?
  • firebase 实际上是实时更新,如果任何节点的任何值发生任何变化,它的处理程序在这里起作用,此时您需要管理您的函数以处理您需要放置的实时更新检查代码,在任何情况下,类似的值都不会再次附加,您可以使用比较器或在处理程序执行之前第二次清除数组。在你的代码中,我检查过你只是在数组中附加值而不是清除它,这就是为什么可能会一次又一次地附加多个相同的值
  • 只需删除观察者之前的所有元素,正如我在回答中提到的那样,它将解决您的问题。如果您仍然遇到问题,请问我,我会向您解释并清除它。
  • 我将 remove all 放在观察者之前,但问题是我的表正在由这个数组填充,所以当它们全部被删除时,它会尝试使用该数组来构建表,现在数组为空,我得到一个超出范围错误的索引。
  • 再看看我编辑了我的答案,并像我一样从故事板分配中删除了委托和数据源
【解决方案3】:

observe(.childAdded) 首先为每个现有的孩子调用一次,然后为每个添加的孩子调用一次。 由于我也遇到了类似的问题,假设您不想显示重复的对象,我认为最好的方法(上面的答案中仍未列出)是为数据库中的每个对象添加一个唯一的 id,然后,对于 observe(.childAdded) 方法检索到的每个对象,检查包含所有对象的数组是否已经包含具有相同 ID 的对象。如果它已经存在于数组中,则无需附加它并重新加载 TableView。当然observe(.childAdded)也必须从viewDidLoad()移到它所属的viewWillAppear(),并且必须在viewDidDisappear中移除观察者。要检查数组是否已经包含检索到的特定对象,在转换 snapshot 之后,您可以使用方法 yourArray.contains(where: {($0.id == retrievedObject.id)})

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多