0

I have a messaging app which contains a tab bar controller, one tab is the messaging view which I built using JSQMessagesViewController. When I navigate to another tab and come back to the messaging view, all my messages are duplicated in the view.

Also when a user send a message, it send multiple instances of the same message, which is obviously not optimal for a messaging app.

I tried putting messages.removeAll() right before observeMessages() in my viewDidAppear like a user suggested in this post and it worked for a few seconds, however eventually this made my app crash and the following message was displayed in the console: fatal error: Index out of range

Here is my code for the said ViewController

class ChatViewController: JSQMessagesViewController, CLLocationManagerDelegate {

    // MARK: Properties

    //Location
    var city: String = ""
    var state: String = ""
    var country: String = ""
    var locationManager = CLLocationManager()
    var locationId: String = ""

     func getLocation() -> String {
        if city == ("") && state == ("") && country == (""){
            return "Anonymous"
        }
        else {
            if country == ("United States") {
                return self.city + ", " + self.state
            }
            else {
                return self.city + ", " + self.state + ", " + self.country
            }
        }
     }

    //Firebase
    var rootRef = FIRDatabase.database().reference()
    var messageRef: FIRDatabaseReference!
    var locationRef: FIRDatabaseReference!

    //JSQMessages
    var messages = [JSQMessage]()

    var outgoingBubbleImageView: JSQMessagesBubbleImage!
    var incomingBubbleImageView: JSQMessagesBubbleImage!



    override func viewDidLoad() {
        super.viewDidLoad()

        self.edgesForExtendedLayout = .None

        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()

        if CLLocationManager.locationServicesEnabled() {
            //collect user's location
            locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
            locationManager.requestLocation()
            locationManager.startUpdatingLocation()
        }

        title = "Group Chat"
        setupBubbles()
        // No avatars
        collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
        collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero

        // Remove file upload icon
        self.inputToolbar.contentView.leftBarButtonItem = nil;

        messageRef = rootRef.child("messages")
        locationRef = rootRef.child("locations")
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        messages.removeAll()

        observeMessages()
    }

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


    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        //--- CLGeocode to get address of current location ---//
        CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in

            if let pm = placemarks?.first
            {
                self.displayLocationInfo(pm)
            }

        })

    }


    func displayLocationInfo(placemark: CLPlacemark?)
    {
        if let containsPlacemark = placemark
        {
            //stop updating location
            locationManager.stopUpdatingLocation()

            self.city = (containsPlacemark.locality != nil) ? containsPlacemark.locality! : ""
            self.state = (containsPlacemark.administrativeArea != nil) ? containsPlacemark.administrativeArea! : ""
            self.country = (containsPlacemark.country != nil) ? containsPlacemark.country! : ""

            print(getLocation())

        }

    }


    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("Error while updating location " + error.localizedDescription)
    }


    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] // 1
        if message.senderId == senderId { // 2
            return outgoingBubbleImageView
        } else {
            return incomingBubbleImageView
        }
    }

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

    override func collectionView(collectionView: JSQMessagesCollectionView!,
                                 avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
        return nil
    }

    private func setupBubbles() {
        let factory = JSQMessagesBubbleImageFactory()
        outgoingBubbleImageView = factory.outgoingMessagesBubbleImageWithColor(
            UIColor.jsq_messageBubbleBlueColor())
        incomingBubbleImageView = factory.incomingMessagesBubbleImageWithColor(
            UIColor.jsq_messageBubbleLightGrayColor())
    }

    func addMessage(id: String, text: String) {
        let message = JSQMessage(senderId: id, displayName: "", text: text)

        messages.append(message)
    }

    override func collectionView(collectionView: UICollectionView,
                                 cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath)
            as! JSQMessagesCollectionViewCell

        let message = messages[indexPath.item]

        if message.senderId == senderId {
            cell.textView!.textColor = UIColor.whiteColor()
        } else {
            cell.textView!.textColor = UIColor.blackColor()
        }

        return cell
    }

    override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!,
                                     senderDisplayName: String!, date: NSDate!) {

        self.edgesForExtendedLayout = .None

        let itemRef = messageRef.childByAutoId()
        let messageItem = [
            "text": text,
            "senderId": senderId
        ]
        itemRef.setValue(messageItem)

        let locRef = locationRef.childByAutoId()
        let locItem = [
            senderId : [
                "location": getLocation()
            ]
        ]

        locRef.setValue(locItem)

        JSQSystemSoundPlayer.jsq_playMessageSentSound()

        finishSendingMessage()

    }

    private func observeMessages() {

        let messagesQuery = messageRef.queryLimitedToLast(25)

        messagesQuery.observeEventType(.ChildAdded) { (snapshot: FIRDataSnapshot!) in

            let id = snapshot.value!["senderId"] as! String
            let text = snapshot.value!["text"] as! String

            self.addMessage(id, text: text)

            self.finishReceivingMessage()
        }
    }


    override func textViewDidChange(textView: UITextView) {
        super.textViewDidChange(textView)
    }


    override func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForCellBottomLabelAtIndexPath indexPath: NSIndexPath!) -> NSAttributedString! {

        let message = messages[indexPath.item]

        // Call data I have retrieved below with message 
        let text = "From: " + getLocation()

        if message.senderId == senderId {
            return nil
        } else {
            return NSAttributedString(string: text)
        }

    }

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


}
Community
  • 1
  • 1
Ahad Sheriff
  • 1,829
  • 8
  • 24
  • 46
  • Can you put a print statement in your firebase function and see how many times its being executed? Let me know what you find out. – Avinash12388 Jul 14 '16 at 01:20
  • I don't think I have a firebase function... – Ahad Sheriff Jul 14 '16 at 01:29
  • Interesting...I don't have a firebase function but I do have a print statement inside the displayLocation function. this print statement is supposed to print out the user's location to the console, however for some reason the information to the console three times. I'm not sure if that may help? – Ahad Sheriff Jul 14 '16 at 02:17
  • My bad. Place the print statement in your FIRDatasnapshot Function, before you set "let id = ..." – Avinash12388 Jul 14 '16 at 21:21
  • Thanks. So I tried what you suggested and saw weird stuff. I printed "message" at the location you told me to and it displayed the exact number of messages that were displayed in the view. When I went to another tab and returned, the number of messages in the view doubled but the print statement did not run. However, when I sent a single message, the print statement ran twice and the message was displayed twice. I went to the next tab again, came back and repeated the action but this time the print ran three times and displayed three times. This sequence continues forever. Weird. – Ahad Sheriff Jul 14 '16 at 22:35

3 Answers3

1

Move observeMessages() from viewDidAppear() and place it in viewDidLoad()

Promise it works when switching back and forth between different views or a segue to a UITableViewController.

Not exactly sure why this is, but I had the same issue and just managed to resolve it, no more duplicate bubbles! I thought it was a problem in relation to UICollectionViewCell but seems like an easy fix.

Let me know if that works for you.

0

In the viewDidDisappear function call the removeAllObservers method.

0

I had (have) the duplicate message problem. The entries in the firebase db are fine, no duplicates there. The "observe child added" function is producing duplicates even though there is only one child added to the firebase DB. I checked the key of each of the duplicates and they were identical. So, now I just check to see if a message has the same key as the previous message and filter it out if it does, and now things work fine -- a bit of a "hack" but I could not get anything else to work.