0

I am using firebase realtime database and implementing user profile data with usersFriend and location. I need to implement the update in object array and show updated values in tableview. I have tried but I am not successful in updating object and then tableview reload. Function already developed.

I need to show updated object array swapped with new values and display in tableview.

var myFriendsDataSource = [FriendClass]()

func watchForChangesInMyFriends() { 
  let usersRef = self.ref.child("profiles") usersRef.observe(.childChanged, with: { snapshot in 
    let key = snapshot.key 
    if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) { 
      let friend = self.myFriendsDataSource[friendIndex] 
      print("found user \(friend.batteryStatus), updating")
      self.myFriendsDataSource[friendIndex] = friend 
      self.tableView.reloadData() 
    } 
  }) 
}

Class:

class FriendClass {

    var uid = ""
    var name = ""
    var batteryStatus = Int()
    var latitude = Double()
    var longitude = Double()
    var timeStamp = Int64()

    //var profilePic

    init(withSnapshot: DataSnapshot) {

        self.uid = withSnapshot.key
        self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
        self.batteryStatus = withSnapshot.childSnapshot(forPath: "batteryStatus").value as? Int ?? 0
        self.latitude = withSnapshot.childSnapshot(forPath: "latitude").value as? Double ?? 0.0
        self.longitude = withSnapshot.childSnapshot(forPath: "longitude").value as? Double ?? 0.0
        self.timeStamp = withSnapshot.childSnapshot(forPath: "timeStamp").value as? Int64  ?? 0

    }
}

Updated:

func loadUsersFriends() {

let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
let myFriendsRef = self.ref.child("userFriends").child(uid)
myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
let uidArray = snapshot.children.allObjects as! [DataSnapshot]
   for friendsUid in uidArray {
          self.loadFriend(withUid: friendsUid.key)
          print(friendsUid)
         }
     })
 }

func loadFriend(withUid: String) {
  let thisUserRef = self.ref.child("profiles").child(withUid)
  thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
  let aFriend = FriendClass(withSnapshot: snapshot)
  self.myFriendsDataSource.append(aFriend)
  print(self.myFriendsDataSource)
  self.tableView.reloadData()
  self.watchForChangesInMyFriends()

        })
    }

Update 2:

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 70
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 10
    }
}

extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myFriendsDataSource.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "FriendListTableViewCell", for: indexPath) as! FriendListTableViewCell
        let dic = myFriendsDataSource[indexPath.row]

        cell.frndName.text = dic.name

        return cell
    }

Newbie
  • 360
  • 3
  • 19
  • Without seeing your tableView code it's not possible to advise on why the tableView is not reloading properly. However you seem to have a logic error in your `watchFor...` function: you get the `friendIndex`, set `friend` = value at that index, and then without changing friend, set value at that index back to `friend`. Basically you set something equal to itself. Did you mean to update the `friend` before putting it back into the array? – flanker Oct 30 '19 at 11:30
  • @flanker I have updated my question and added two main functions which are `loadFriend` and `loadUsersFriends`. About `friend` contains matched values from model which needs to be change. I would need to read the info from the snapshot, update that object with the new info in your array and then reload the tableView. `watchFor...` already have new snapshot. – Newbie Oct 30 '19 at 11:47
  • If I'm reading it correctly, the `watchFor...` method registers a closure that gets called whenever the underlying firebase data changes, and when the closure is called it is provided with a snapshot of that change and then you want to update your `myFriendsDataSource` array with the new data? Correct? At the moment you are finding the array element for the `friend`, printing it out, but not actually updating it with the new data from the snapshot? Therefore, assuming the `tableView` datasource is the `myFriendsDataSource` array, there will be nothing to update in the `tableView` – flanker Oct 30 '19 at 12:06
  • @flanker yes exactly your analysis is correct. – Newbie Oct 30 '19 at 12:19
  • @flanker is correct. You're doing this `let friend = self.myFriendsDataSource[friendIndex]` and then taking that friend from the array and putting it right back in with this `self.myFriendsDataSource[friendIndex] = friend` whereas what you want to do is `self.myFriendsDataSource[friendIndex] = data from snapshot` – Jay Oct 30 '19 at 12:20

1 Answers1

3

Given the above comment discussion, I think you need to update your watchForChangesInMyFriends method as below to actually update the datasource with the new friend data. You should also do all your UI updates on the main thread, and as there is no guarantee that this closure will run on the main thread you need to force the tableView update onto the main thread.

func watchForChangesInMyFriends() { 
  let usersRef = self.ref.child("profiles") usersRef.observe(.childChanged, with: { snapshot in 
    let key = snapshot.key 
    if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) { 
      let friend = self.myFriendsDataSource[friendIndex] 
      print("found user \(friend.batteryStatus), updating")
      self.myFriendsDataSource[friendIndex] = FriendClass(withSnaphot: snapshot)
      DispatchQueue.main.async {
        self.tableView.reloadData()
      } 
    } 
  }) 
}

It's also better practice to update just the tableView data that has changed rather than reloading the whole tableView. You can probably use the array index to generate an IndexPath for the appropriate row and then just reload that row. Without seeing your tableView methods I can't be precise, but it'll probably look something like this:

let indexPath = IndexPath(row: friendIndex, section: 0)
DispatchQueue.main.async {
  self.tableView.reloadRows(at: [indexPath], with: .automatic)
}
flanker
  • 3,840
  • 1
  • 12
  • 20
  • Thank you works perfectly with `self.tableView.reloadData()`. But for `tableView.reloadRows` its not working. I have updated question with my tableview methods. – Newbie Oct 30 '19 at 12:40
  • it'll need the `self.` before the reloadRows as well. If it fails, share the error message – flanker Oct 30 '19 at 13:15
  • I used `self.` before otherwise red warning comes in Xcode. But now I tried again & its working. Both ways working with success. Perfect and many many thanks. :) – Newbie Oct 30 '19 at 13:25
  • 1
    @Newbie This is not needed in Firebase closures `DispatchQueue.main.async {`. All UI calls within Firebase closures are done on the main thread. See [this answer](https://stackoverflow.com/questions/56314265/how-to-display-data-from-firebase-faster/56314312#56314312) from a Firebaser for more info. – Jay Oct 30 '19 at 15:40
  • 1
    Thanks @Jay. I've never used Firebase, and was just treating it as a standard closure, but happy to concede to those that have :) – flanker Oct 30 '19 at 15:51