Ahad Sheriff Ahad Sheriff - 4 months ago 38
Swift Question

Displaying user location

I have a a simple messaging app that I built using the JSQMessagesViewController framework. I have been trying to use CoreLocation to collect a user's location and I want to display that location under the actual message content (

attributedTextForCellBottomLabelAtIndexPath
). I have figured all of that out, however the problem is each location needs to be unique to each individual message and that is not working.

For example, if a user sends a message from Seattle, it should say "Seattle" under their message. And if another user send another message for San Fransisco, it should say "San Fransisco" under their message.

What is happening now, is that all the messages say they are from the location where the user currently is from (assume they are in Seattle). So it doesn't matter if the other person is in San Fransisco, the message will show that it is from Seattle.

It is a pretty confusing problem, but an annoying one that I assume should have a simple fix...

Here is the code for
attributedTextForCellBottomLabelAtIndexPath
:

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

let message = messages[indexPath.item]

// Call data I have retrieved below with message
let text = getLocation()

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

}


Note: I have a function called
getLocation()
that simply returns the user's city/state/country


And here is the code for the rest of the class
ChatViewController
for context:

/*
* Copyright (c) 2016 Ahad Sheriff
*/

import UIKit
import Firebase
import JSQMessagesViewController
import CoreLocation

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!

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

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

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

// Remove file upload icon
self.inputToolbar.contentView.leftBarButtonItem = nil;
// Send button
self.inputToolbar.contentView.rightBarButtonItem.setTitle("Send", forState: UIControlState.Normal)

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! : ""

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 { // 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(
purp)
incomingBubbleImageView = factory.incomingMessagesBubbleImageWithColor(
redish)
}

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
]
itemRef.setValue(messageItem) // 3

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

locRef.setValue(locItem)

// 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! {

let message = messages[indexPath.item]

// Call data I have retrieved below with message
let text = 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
}

}


Note: You can also ignore the code where I am saving user location to Firebase.

Thanks for any help in advance! Please let me know if you have any further questions.

Answer

Your problem is that you are setting the bottom message label with the client not the data from the message. So every bottom label is set by the user not the originator of the message.

I don't know if that made since but essentially you are getting location data locally so if you have a conversation between Mark, who is in Seattle, and Deric who is not in Seattle.

EX.Conversation

Mark Hi

Deric Wazzup

it will always say Seattle because you are getting location data from the client locally and are setting it in this function.

func getLocation()

So when you look at the conversation from Mark's phone you are setting the location data based on Mark's location even for the message from 'Deric'.

So the way to solve this is to save the location data to the message and store it in firebase. Then change this function to pull that data for your bottom label. Something along these lines.

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

let message = messages[indexPath.item]

// Call data I have retrieved below with message
let text = message.location   <- Here is a suggested solution ****

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

}

I hope that was clear. I know its a confusing question and an almost as confusing answer. But essentially you need to save the location to the messageData post it to your backend firebase and then display that for the given message.

I think you were definitely on the right path just missing that one piece.