jessesquires / JSQMessagesViewController

An elegant messages UI library for iOS

Home Page:https://www.jessesquires.com/blog/officially-deprecating-jsqmessagesviewcontroller/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Changing bubble color depending on gender of message sender

tHull90 opened this issue · comments

I'm using Swift 3...

I'm trying to figure out how I can use a genderDictionary that I have (in the form of [UID: Gender]) for all participants in a chat) to determine the background color of the message bubble.

I can use a switch statement in setupIncomingBubble() like so:

    switch gender {
    case "male":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "2573C5"))
    case "female":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "E452CE"))
    default:
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "848484"))
    }

However since I don't have the indexPath for the messages, I don't have a way to tell who is sending each message. I'd like to use something like:

    let message = messages[indexPath.row]
    let gender = genderDictionary.value(forKey: message.senderId)

But setupIncomingBubble() doesn't have an indexPath. Is there another way to go about this, or simply a way to get that indexPath? Sorry if this is vague, and let me know if you need more information - I'm a beginner to programming in general, and this is my first project with this framework.

Thanks for any help!

This should work:

var outgoingBubble = setupOutgoingBubble()
var incomingBubble = setupIncomingBubble(nil)
var maleIncomingBubble = setupIncomingBubble("male")
var femaleIncomingBubble = setupIncomingBubble("female")

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
    let message = messages[indexPath.row]
    let gender = genderDictionary.value(forKey: message.senderId)

    if message.senderId() == senderId {
        return outgoingBubble
    }

    switch gender {
    case "male":
        return maleIncomingBubble
    case "female":
        return femaleIncomingBubble
    default:
        return incomingBubble
    }
}

Hey acth, thanks! I'm getting an error on all the variables saying "Cannot use instance member 'whatever the variable is' within property initializer; property initializers run before 'self' is available"

Also, what exactly would my setupIncomingBubble function look like? I can't have the switch statement in there because I can't get the gender of the incoming message sender (need indexPath).

lazy var outgoingBubble = setupOutgoingBubble()
lazy var incomingBubble = setupIncomingBubble(nil)
lazy var maleIncomingBubble = setupIncomingBubble("male")
lazy var femaleIncomingBubble = setupIncomingBubble("female")

func setupIncomingBubble(gender: String?) -> JSQMessagesBubbleImage {
    switch gender {
    case "male":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "2573C5"))
    case "female":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "E452CE"))
    default:
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "848484"))
    }
}

Thanks, I actually got it working differently - I put the variables inside the function, so my final function looks like this:

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
    let message = messages[indexPath.row]
    let gender = genderDictionary.value(forKey: message.senderId) as! String
    
    var outgoingBubble = self.setupOutgoingBubble()
    // var incomingBubble = self.setupIncomingBubble(gender: nil) <- Error
    var maleIncomingBubble = self.setupIncomingBubble(gender: "male")
    var femaleIncomingBubble = self.setupIncomingBubble(gender: "female")
    
    if message.senderId == senderId {
        return outgoingBubble
    }
    
    switch gender {
    case "male":
        return maleIncomingBubble
    case "female":
        return femaleIncomingBubble
    default:
        return maleIncomingBubble
    }
}

It works now, except I get an error on the var incomingBubble = self.setupIncomingBubble(gender: nil) line, "nil is not compatible with type string". I commented it out because users provide their gender at sign up, so I can't think of a scenario where it wouldn't be male or female, and if they selected "other" at sign up, it will go to the default grey color.

It's advisable to setup bubbles once, instead of assigning to local variables each time the function is called.

I tried your new code and am still getting the same error on those lines. Also "unexpected non-void return value in void function" errors on the returns in setupIncomingBubble.

lazy var outgoingBubble = setupOutgoingBubble()
lazy var incomingBubble = setupIncomingBubble(nil)
lazy var maleIncomingBubble = setupIncomingBubble("male")
lazy var femaleIncomingBubble = setupIncomingBubble("female")

func setupIncomingBubble(gender: String?) {
    switch gender {
    case "male":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "2573C5"))
    case "female":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "E452CE"))
    default:
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "848484"))
    }
}

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
    let message = messages[indexPath.row]
    let gender = genderDictionary.value(forKey: message.senderId)
    
    if message.senderId == senderId {
        return outgoingBubble
    }
    
    switch gender {
    case "male":
        return maleIncomingBubble
    case "female":
        return femaleIncomingBubble
    default:
        return incomingBubble
    }
}

Add return type -> JSQMessagesBubbleImage

Yeah so I got it to run, but I get an "unexpectedly found nil" crash as soon as the chat is opened. This is my code:

lazy var outgoingBubble: JSQMessagesBubbleImage = self.setupOutgoingBubble()
//lazy var incomingBubble: JSQMessagesBubbleImage = self.setupIncomingBubble(gender: nil)
lazy var maleIncomingBubble: JSQMessagesBubbleImage = self.setupIncomingBubble(gender: "male")
lazy var femaleIncomingBubble: JSQMessagesBubbleImage = self.setupIncomingBubble(gender: "female")

func setupIncomingBubble(gender: String) -> JSQMessagesBubbleImage {
    switch gender {
    case "male":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "2573C5"))
    case "female":
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "E452CE"))
    default:
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "848484"))
    }
}

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
    let message = messages[indexPath.row]
    let gender = genderDictionary.value(forKey: message.senderId) as! String
    
    if message.senderId == senderId {
        return outgoingBubble
    }
    
    switch gender {
    case "male":
        return maleIncomingBubble
    case "female":
        return femaleIncomingBubble
    default:
        return maleIncomingBubble
    }
}

Still had to comment out this line: lazy var incomingBubble: JSQMessagesBubbleImage = self.setupIncomingBubble(gender: nil), I can't pass it nil.

I populate the genderDictionary in the prepareForSegue method of the previous page (the table view that lists the chat rooms). I'm not sure when the messageBubbleImageDataForItemAt method is called, but I'm guessing the crash is because it's looking for genderDictionary values when it's still empty? Again I'm fairly new so I'm not sure.

Which line is the crash on?
You can pass nil if you left it as gender: String?.

It doesn't highlight a line for the crash, but it must be the genderDictionary because I don't get the crash when I open a chat with just me in it (outgoing bubble just checks currentUser.gender to set the color, not the genderDict). The crash only happens in chats where there are users (male & female) other than myself. And I get errors on the switch statement if I don't change it to gender: String!

It may be cleaner to pass in an empty string "" instead of nil.
It's likely that genderDictionary doesn't contain some senderId.

let gender = genderDictionary.value(forKey: message.senderId) as? String ?? ""

Ok cool, I added that and it seems to be working now. As for making the gender string parameter optional (sorry if my terminology is off) in setupIncomingBubble, if I do that, I get an error on the "male" and "female" lines stating Expression pattern of type 'String' cannot match values of type 'String?'. Is there a way to get around that, or should I just leave the function as func setupIncomingBubble(gender: String?) -> JSQMessagesBubbleImage?

This is cleaner:

lazy var incomingBubble: JSQMessagesBubbleImage = self.setupIncomingBubble(gender: "")

func setupIncomingBubble(gender: String) -> JSQMessagesBubbleImage {
    // ...
}

Nice, that's a good fix. I noticed now that sometimes when I run the demo chat, the incoming bubbled are the default grey for a few seconds, then turn pink or blue depending on gender. I guess without that, it would have simply crashed because those values weren't there yet?

For completeness sake:

lazy var incomingBubble: JSQMessagesBubbleImage = self.setupIncomingBubble(gender: nil)

func setupIncomingBubble(gender: String?) -> JSQMessagesBubbleImage {
    switch gender {
    case .some("male"):
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "2573C5"))
    case .some("female"):
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "E452CE"))
    default:
        return JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(hexString: "848484"))
    }
}

Interesting, I've never seen that before. What does .some do?

The type Optional<Wrapped> is an enum with two cases, none and some(Wrapped). So .some(value) can be used for pattern matching in a switch statement.

Cool - thanks for your help on this!

You're welcome!

@acjh, thanks so much. You rock! 💯 👍 🥇