Search code examples
swiftfirebasefirebase-realtime-databasejsqmessagesviewcontroller

Text retrieval from Firebase is not displaying in app


I am working on a messaging app and I want to display the location where a message was sent from under the message content. I try to do this by sending the user's location data to Firebase, and then attempting to retrieve the data to display it as a string.

Getting the users location works fine (I am using CoreLocation to do so) as does uploading data to my Firebase realtime database. I save the location along with each message as such:

let itemRef = messageRef.childByAutoId() // 1 let messageItem = [ // 2 "text": text, "senderId": senderId, "location": getLocation() ] itemRef.setValue(messageItem) // 3

And then attempt to retrieve the data in another method as such:

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

        var locationId: String = ""
        let messagesQuery = messageRef
        let message = messages[indexPath.item]

        messagesQuery.observeEventType(.ChildAdded) { (snapshot: FIRDataSnapshot!) in
            locationId = snapshot.value!["location"] as! String
            print(locationId)
        }

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

    }

The correct location gets printed out to the console but nothing gets displayed in my app. However if I replace the variable locationId with any other string, it works.

My problem I believe is with Firebase retrieval.

If anybody could help me fix this issue, that would be greatly appreciated.

Here is the rest of the code for my class for reference (just in case):

class ChatViewController: JSQMessagesViewController, CLLocationManagerDelegate {

    // MARK: Properties

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

    //JSQMessages
    var messages = [JSQMessage]()
    var outgoingBubbleImageView: JSQMessagesBubbleImage!
    var incomingBubbleImageView: JSQMessagesBubbleImage!

    var purp = UIColor.init(red:47/255, green: 53/255, blue: 144/255, alpha: 1)
    var roastish = UIColor.init(red: 255/255, green: 35/255, blue: 35/255, alpha: 1.0)
    var orangish = UIColor.init(red: 231/255, green: 83/255, blue: 55/255, alpha: 1.0)
    var gray = UIColor.init(red: 241/255, green: 251/255, blue: 241/255, alpha: 1)

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

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

    override func viewDidLoad() {
        super.viewDidLoad()

        // Initialize location
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()

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

        // Change the navigation bar background color
        navigationController!.navigationBar.barTintColor = gray

        self.navigationController!.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont(name: "Avenir Next", size: 20)!]

        title = "RoastChat"
        setupBubbles()
        // No avatars

        // Remove file upload icon
        self.inputToolbar.contentView.leftBarButtonItem = nil;
        // Send button
        self.inputToolbar.contentView.rightBarButtonItem.setTitle("Roast", forState: UIControlState.Normal)
        // Send button color
        self.inputToolbar.contentView.rightBarButtonItem.setTitleColor(roastish, forState: UIControlState.Normal)
        // Input bar text placeholder
        self.inputToolbar.contentView.textView.placeHolder = "RoastChat"

        collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
        collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero

        //Firebase reference
        messageRef = rootRef.child("messages")
        locationRef = rootRef.child("locations")

    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        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! : ""

        }

    }


    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 { // 3
            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(
            roastish)
    }

    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.whiteColor()
        }

        return cell
    }

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

        let itemRef = messageRef.childByAutoId() // 1
        let messageItem = [ // 2
            "text": text,
            "senderId": senderId,
            "location": getLocation()
        ]
        itemRef.setValue(messageItem) // 3

        // 4
        JSQSystemSoundPlayer.jsq_playMessageSentSound()

        // 5
        finishSendingMessage()

        Answers.logCustomEventWithName("Message sent", customAttributes: nil)

    }

    private func observeMessages() {
        // 1
        let messagesQuery = messageRef.queryLimitedToLast(25)
        // 2
        messagesQuery.observeEventType(.ChildAdded) { (snapshot: FIRDataSnapshot!) in
            // 3
            let id = snapshot.value!["senderId"] as! String
            let text = snapshot.value!["text"] as! String

            // 4
            self.addMessage(id, text: text)

            // 5
            self.finishReceivingMessage()

            Answers.logCustomEventWithName("Visited RoastChat", customAttributes: nil)

        }
    }

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

        var locationId: String = ""
        let messagesQuery = messageRef
        let message = messages[indexPath.item]

        messagesQuery.observeEventType(.ChildAdded) { (snapshot: FIRDataSnapshot!) in
            locationId = snapshot.value!["location"] as! String
            print(locationId)
        }

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

    }

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


    override func collectionView(collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAtIndexPath indexPath: NSIndexPath!) {

        super.collectionView(collectionView, didTapMessageBubbleAtIndexPath: indexPath)
        let data = self.messages[indexPath.row]
        print("They tapped: "  + (data.text))

    }

}

Solution

  • I wasn't able to do the retrieval any other way. Instead I exploited JSQMessagesViewControllers other built in methods to solve my problem. The framework has a built in variable called senderDisplayName. Since the nature of this app I am making is anonymous I didn't need to use it, so I manipulated the senderDisplay name to display the user's location instead.

    func addMessage(id: String, text: String, displayName: String) {
        let message = JSQMessage(senderId: id, displayName: displayName, text: text)
        messages.append(message)
    }
    

    Then I set displayName to be the location:

    private func observeMessages() {
            // 1
            let messagesQuery = messageRef.queryLimitedToLast(25)
            // 2
            messagesQuery.observeEventType(.ChildAdded) { (snapshot: FIRDataSnapshot!) in
                // 3
                let id = snapshot.value!["senderId"] as! String
                let text = snapshot.value!["text"] as! String
                let locationId = snapshot.value!["location"] as! String
    
                // 4
                // self.addMessage(id, text: locationId.lowercaseString + ": \n" + text)
                self.addMessage(id, text: text, displayName: locationId)
    
    
                // 5
                self.finishReceivingMessage()
    
                Answers.logCustomEventWithName("Visited RoastChat", customAttributes: nil)
    
            }
        }
    

    Then I just used the built in attributedTextForCellBottomLabelAtIndexPath to set my bottom label as the user's location:

    override func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForCellBottomLabelAtIndexPath indexPath: NSIndexPath!) -> NSAttributedString! {
    
        let message = messages[indexPath.item]
    
        if message.senderId == senderId {
            return nil
        } else {
            return NSAttributedString(string: message.senderDisplayName)
        }
    
    }
    

    It works perfect! This is what programming is all about, problem solving.

    Thanks to everyone who helped and pointed me in the right direction.