0

I'm seeking to populate the tableview of my users with the last message they sent or were sent just like the initial View on Messages App on iphone. I have this code but it doesn't work to iterate through the array of dictionaries that I keep snapshots in of all the messages that were sent to the user and return the expected result:

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

 func getMsg()-> Int{
  var getIndex = Int()
  let messageDb = Database.database().reference().child("iMessenger/Messages")
  messageDb.observe(.childAdded) { (snapshot) in

  if let snapshotvalue = snapshot.value as? [String:Any], let sender = snapshotvalue["sender"] as? String, let key = snapshotvalue["key"] as? String{

 // I thought that I could iterate through my array of dictionaries taking care to find a matching sender value for the user at indexpath.row and the key for the message

  for (index, dict) in newMessageDictArr.enumerated(){
  if self.users[indexPath.row].userPhone == sender && dict["key"] == key{getIndex = index}}}}

  return getIndex}


 if newMessageDictArr.count !=  0{
 cell.userMessage.text = newMessageDictArr[getMsg()]["messageBody"]}

 return cell}

The problem I'm having with the code is that

cell.userMessage.text = newMessageDictArr[getMsg()]["messageBody"]

Makes all my cells have the first value for ["messageBody"] so it's like all my users seemed to have sent me the same message. Trying to figure out where I went wrong...

Also here's my FireBase structure, thanks.

{
"iMessenger" : {
"Messages" : {
    "-KxDSQOBuCkpFdw4SPDh" : {
      "messageBody" : "TEST ",
      "receiver" : "+197862",
      "sender" : "+16698"  
  },
  "-KxBjNM_iA2XaapyyC9o" : {
    "key" : "-KxBjNM_iA2XaapyyC9o",
    "messageBody" : "TEST",
    "receiver" : "+197862",
    "sender" : "+1914862"
      },
"Messages+199862+197862" : {
  "-KxB7P-vgdfxRpSdnwCT" : {
    "messageBody" : "yo, wassup world",
    "receiver" : "+197862",
    "sender" : "+199862"
  },
  "-KxB81fbn5_OHxdj4lcA" : {
    "messageBody" : "Hello World",
    "receiver" : "+19147862",
    "sender" : "+1997862"
  }
},
"Users" : {
  "-Kx41o03NIMBR68F2sCS" : {
    "displayName" : "sleeping beauty ",
    "firstName" : "",
    "lastName" : "",
    "phoneNumber" : "+165698"
  },
  "-Kx42IXgN1z9D5Zoz0fk" : {
    "displayName" : "KarmaDeli",
    "firstName" : "",
    "lastName" : "",
    "phoneNumber" : "+1397862"
  }}}}
KarmaDeli
  • 610
  • 1
  • 6
  • 16
  • This code is not going to work correctly, cause weird UI issues and probably be a lot of work in the long run. The concept is you attach an observer to a node and when your app receives an event that data was added, changed or removed, update your tableView datasource accordingly (usually an array) then reload your tableView which will populate the table from that array. You do NOT want to add observers within the tableView:cellForRowAt method. The question states to populate with the last message they sent or were sent. Do you literally mean you have a single message in the tableView? – Jay Oct 24 '17 at 17:44
  • Oh, and please add your Firebase structure as TEXT please, no images so we can properly formulate an answer. You can get your Firebase structure via the Firebase console->Export JSON. Just include a small snippet so we can understand what you are trying to query for. (include as TEXT!) – Jay Oct 24 '17 at 17:47
  • ok so the taking a snapshot in the tableview was a bad idea-- my tableView takes data from 2 fireBase locations iMessenger/Users and iMessenger/Messages currently. Should I find a way to populate the tableview from one database only? What I meant was when I run the app on my phone I see the first messageBody["Hello world"] for example, in the cell.message.text for ever user on that tableVIew no matter how many new messages are sent. – KarmaDeli Oct 24 '17 at 18:13

1 Answers1

0

The question is a bit unclear but let me give it a shot:

Conceptually, a tableView is backed by an array of data. When that data changes the array should be updated, and then tableView.reloadData should be called to refresh what's in the tableView. In general, that technique provides the smoothest experience for the user.

IMO, the code should be rewritten to follow that design pattern. I'm not going to write all the code but will provide a flow and a couple of code snippets to give you some direction. These are not tested so don't copy and paste.

First, start with two classes

class MessageClass {
  var msgId = ""
  var msgBody = ""
  var msgSenderUid = ""
  var msgReceiverUid = ""
}

class UserClass {
  var uid = ""
  var name = ""
  func init(aUid: String, aName: String) {
       self.uid = aUid
       self.name = aName
  }
}

then two arrays to keep track of data - the first, messagesArray, will be the dataSource for your tableView

var messagesArray = [MessageClass]()
var usersArray = [UsersClass]()

We're going to assume the app has a couple hundred users so we're just going to load all of the user data in on app start. Keep in mind that if a new user is added, the usersArray will be updated with the additional user automatically

func viewDidLoad() {
   let ref = firebaseRef.child("users")
   ref.observe(.childAdded..... { snapshot in
       let userDict = snapshot.value as! [String: Any]
       let uid = userDict.key
       let name = userDict["name"] as! String
       let userClass = User(aUid: uid, aName: name)
       self.usersArray.append(userClass)

and also add an observer to the messages node so your app will be notified of new messages. We are going to leverage a Firebase query observer so this user will only be notified of messages meant for them

   let queryRef = firebaseRef.child("messages")
   queryRef.queryOrdered(byChild: "msg_receiver_uid").query(equalTo: thisUsersUid) {
       let dict = snapshot.value as! [String: Any]
       let sendersUid = dict["msgSenderUid"] as! String
       let msgText = dict["msgBody"] as! String
       //create a MessageClass object and populate it with data
       //add it to the messagesArray
       tableView.reloadData()

then finally, in your tableView functions, get each row from the dataSource messagesArray and create and populate each cell.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
   let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
     let msg = self.messagesArray[indexPath.row]
     let msgSenderUid = msg.msgSenderUid
     let sendersName = //some function to look up the senders name from the users array
     let msg = msg.msgBody
     //populate cell with  users name and msg body
     return cell

Again, this is not meant for copy/paste but to provide an example flow.

As new messages are posted, if the message is for this user, they will be notified of the message via the query observer and presented the message in an event. The messages is stuffed into a message class object, added to the datasource array and the tableview is updated to display it.

One caveaut to this is if you don't want to load all of the users. In that case, when a message event occurs, perform a observeSingleEvent on the users node to retreive the user info. Then within that closure, build the messageClass, add to array and reload the tableView.

Hope that helps.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • You're definitely right, per tableview bespoke objects and the arrays that contain them are the way to go. I kinda got stuck in the way of think that so long as I have the snapshot dictionary values pulled down from Firebase I could do with I will with the data. I understand now that tableviews and arrays go to together like PB and J. Instead of using queryOrderedID, as I am not familiar it, I used some booleans to determined if the messages were sent to or from the user. The logic was flawed at high level at first and that produced some bugs. It's all fixed now though, thanks again. – KarmaDeli Oct 26 '17 at 12:49