Ahad Sheriff Ahad Sheriff - 4 months ago 13
Swift Question

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))

}

}

Answer

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.