1

I am attempting to implement JSQMesages to my app, however, I can't figure out why when I press the send button all of the messages disappear. I have went step by step and can't figure it out, when the chat initially loads, everything is there. But then the send button does something that makes it all vanish?

Any ideas?

//
//  JSQMessagesViewController.swift
//  ACSS
//
//  Created by Trever on 10/21/15.
//  Copyright © 2015 trever.me. All rights reserved.
//

    import UIKit

    class MessagesViewController: JSQMessagesViewController {

    // Refresh Messages
    var timer : NSTimer!

    // Don't double load!
    var isLoading = false

    // ID for this chatroom. Can have mutliple users
    var chatroom = ""

    // Currently only set up for two people
    var currentUser : TradeUser?
    var otherUser : PFUser?

    //Value collection so don't double load too much
    var avatars = [String:JSQMessagesAvatarImage]()
    //Array of messages
    var messages = [JSQMessage]()

    //Collection of users
    var users = [String : PFObject]()

    // Chat bubbles for conversation
    var outgoingBubbleImageView : JSQMessagesBubbleImage!
    var incomingBubbleImageView : JSQMessagesBubbleImage!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create a chatroom for each pair. Same for both
        // Smaller ID first
        self.senderId = TradeUser.currentUser()?.objectId
        self.chatroom = currentUser!.objectId > otherUser!.objectId ?
            "\(currentUser!.objectId!)-\(otherUser!.objectId!)" :
        "\(otherUser!.objectId!)-\(currentUser!.objectId!)"

        //Setup chat bubbles
        let bubbleFactory = JSQMessagesBubbleImageFactory()
        outgoingBubbleImageView = bubbleFactory.outgoingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleLightGrayColor())
        incomingBubbleImageView = bubbleFactory.incomingMessagesBubbleImageWithColor(UIColor.jsq_messageBubbleBlueColor())

        //Loading...
        isLoading = false
        self.loadMessages()

        //Check for new messages every 5 seconds
        timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "loadMessages", userInfo: nil, repeats: true)

        self.senderDisplayName = "test"

    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(true)
        self.collectionView?.collectionViewLayout.springinessEnabled = true
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        // when they leave this screen, stop checking for messages
        timer.invalidate()
    }

    func loadMessages() {
        if !isLoading {
            isLoading = true
            let message_last = messages.last

            //Fetch Messages
            let query = TradeChat.query()!
            query.whereKey("chatRoom", equalTo: chatroom)

            //Time Base Pagination
            if message_last != nil {
                query.whereKey("createdAt", greaterThan: message_last!.date)
            }

            // Get senders ID
            query.includeKey("sender")

            //Show Messages in order
            query.orderByAscending("createdAt")
            query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
                if error == nil {
                    for object in objects! {
                        // Go through each message and create a message for display
                        let tradeChat = object as! PFObject
                        let senderObject = tradeChat.objectForKey("sender") as! PFObject
                        let senderFirstName = senderObject.objectForKey("firstName") as! String
                        let createdAt = tradeChat.createdAt
                        let text = tradeChat.objectForKey("chatText") as! String

                        let message = JSQTextMessage(senderId: senderObject.objectId!, senderDisplayName: senderFirstName, date: createdAt, text: text)

                        self.messages.append(message)
                        print(self.messages.count)

                        //Chache the user object for later
                        self.users[senderObject.objectId!] = senderObject

                    }
                    if !objects!.isEmpty {
                        self.finishReceivingMessage()
                    }
                }
                self.isLoading = false
            })
        }
    }

    override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
        // When they hit send. Save the message
        let tradeChat = TradeChat()
        tradeChat.chatRoom = chatroom
        tradeChat.sender = TradeUser.currentUser()!
        tradeChat.chatText = text

        tradeChat.saveInBackgroundWithBlock { (succeeded, error) -> Void in
            if error == nil {
                JSQSystemSoundPlayer.jsq_playMessageSentSound()
                self.loadMessages()
            }
        }

        self.finishSendingMessage()

    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
        return messages[indexPath.item]
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
        let message = messages[indexPath.item]

        if message.senderId == self.senderId {
            return outgoingBubbleImageView
        } else {
            return incomingBubbleImageView
        }
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
        let message = messages[indexPath.item]
        if self.avatars[message.senderId] == nil {
            var imageView = JSQMessagesAvatarImage(placeholder: UIImage(named: "profile"))
            self.avatars[message.senderId] = imageView

            let user = users[message.senderId]!
            print(user)
            var parseAvatar = PFFile()

            var imageQuery = TradeUser.query()!
            imageQuery.whereKey("objectId", equalTo: user.objectId!)
            imageQuery.limit = 1

            imageQuery.findObjectsInBackgroundWithBlock({ (images: [AnyObject]?, error: NSError?) -> Void in
                if imageQuery.countObjects() == 0 {
                    return
                } else {
                    for image in images! {
                        if image.objectForKey("profileImage") == nil {
                            return
                        } else {
                            let userPic = image.objectForKey("profileImage") as! PFFile
                            userPic.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
                                if (error == nil) {
                                    let image = UIImage(data: imageData!)
                                    imageView.avatarImage = JSQMessagesAvatarImageFactory.circularAvatarImage(UIImage(data: imageData!), withDiameter: 30)
                                }
                            })
                        }

                    }
                }
            })

            // Reload entire table now that the avatar is downloaded
            self.collectionView?.reloadData()

        }

        return self.avatars[message.senderId]
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> NSAttributedString! {
        // Show the name every once and a while
        let message = messages[indexPath.item]

        if message.senderId == self.senderId {
            return nil
        }

        if indexPath.item - 1 > 0 {
            let prevMessage = messages[indexPath.item - 1]
            if prevMessage.senderId == message.senderId {
                return nil
            }
        }

        let firstName = otherUser?.objectForKey("firstName") as! String

        return NSAttributedString(string: firstName)
    }

    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        print("Found \(messages.count) messages")
        return messages.count
    }

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        // Grab the cell were about to show
        let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! JSQMessagesCollectionViewCell

        //Customize it some
        let message = messages[indexPath.item]
        if message.senderId == self.senderId {
            cell.textView?.textColor = UIColor.blackColor()
        } else {
            cell.textView?.textColor = UIColor.whiteColor()
        }

        return cell
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellTopLabelAtIndexPath indexPath: NSIndexPath!) -> CGFloat {

        // If were going to show the date/time, give it some height
        if indexPath.item % 3 == 0 {
            return kJSQMessagesCollectionViewCellLabelHeightDefault
        }

        return 0.0

    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAtIndexPath indexPath: NSIndexPath!) -> CGFloat {

        let message = messages[indexPath.item]
        if message.senderId == self.senderId {
            return 0.0
        }

        if indexPath.item - 1 > 0 {
            let previousMessage = messages[indexPath.item - 1]
            if previousMessage.senderId == message.senderId {
                return 0.0
            }
        }

        return kJSQMessagesCollectionViewCellLabelHeightDefault

    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellBottomLabelAtIndexPath indexPath: NSIndexPath!) -> CGFloat {

        // more height logic
        return 0.0
    }

    override func collectionView(collectionView: JSQMessagesCollectionView!, header headerView: JSQMessagesLoadEarlierHeaderView!, didTapLoadEarlierMessagesButton sender: UIButton!) {
        print("tapped load earlier messages - need implementation")
    }

}
trever
  • 961
  • 2
  • 9
  • 28

1 Answers1

0

I think that your issue is in that you don't have a conversation object that holds your conversation locally. So when you press send message you are posting a message to parse and then asking for all the messages in the conversation in a background thread.

 query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
            if error == nil {
                for object in objects! {
                    // Go through each message and create a message for display
                    let tradeChat = object as! PFObject
                    let senderObject = tradeChat.objectForKey("sender") as! PFObject
                    let senderFirstName = senderObject.objectForKey("firstName") as! String
                    let createdAt = tradeChat.createdAt
                    let text = tradeChat.objectForKey("chatText") as! String

                    let message = JSQTextMessage(senderId: senderObject.objectId!, senderDisplayName: senderFirstName, date: createdAt, text: text)

                    self.messages.append(message)
                    print(self.messages.count)

                    //Chache the user object for later
                    self.users[senderObject.objectId!] = senderObject

                }
                if !objects!.isEmpty {
                    self.finishReceivingMessage()
                }
            }

Since this is on a background thread the UI is not going to update because it is on the main thread. If you had a observer or a closure with completion that then called your collection view to reload once every thing was downloaded it may solve your problem.

Or alternatively you could have a conversation object and when you press send you add it to you conversation object locally and post it to your backend. Then on view did load you just pull the conversation from your backend save it to your local copy and display it from there. You would have a instantaneous UI and you could warn the user if the message failed to send and prompt them to send it again.

Dan Leonard
  • 3,325
  • 1
  • 20
  • 32
  • I'm having a similar issue. The initial load of the view controller loads everything fine. After posting several messages (the issue is intermittent) the view will go blank. Each time a message is posted, the database is queried in the background and then the messages array is updated on the main thread followed by a reloadData() called on the main thread. Is there a way to easily debug this further? Where would you look? – TALE Dec 09 '16 at 22:40
  • You should just have a local instance of the messages array and when you press the send button the new message is appended. Then if an error is thrown from your post method notify the user and give them the ability to try again. – Dan Leonard Dec 11 '16 at 05:29