1

I'm developing a chat app, I'm having problem showing the Avatar to my JSQMessagesViewController

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

    var avatar = UIImage()

    let people = FIRDatabase.database().reference().child("people").child(senderId)
    people.observeEventType(.Value, withBlock: {
        snapshot -> Void in
        let dict = snapshot.value as! Dictionary<String, AnyObject>
        let imageUrl = dict["profileImage"] as! String
        if imageUrl.hasPrefix("gs://") {
            FIRStorage.storage().referenceForURL(imageUrl).dataWithMaxSize(INT64_MAX, completion: { (data, error) in
                if let error = error {
                    print("Error downloading: \(error)")
                    return
                }
                avatar = UIImage.init(data: data!)!

            })
        }
    })

    let AvatarJobs = JSQMessagesAvatarImageFactory.avatarImageWithPlaceholder(avatar, diameter: UInt(kJSQMessagesCollectionViewAvatarSizeDefault))

    return AvatarJobs
}

The problem here is, when I'm trying to pull the image of the sender from firebase, I'm getting a blank image, but when i try to use this let AvatarJobs = JSQMessagesAvatarImageFactory.avatarImageWithPlaceholder(UIImage(named: "icon.png"), diameter: UInt(kJSQMessagesCollectionViewAvatarSizeDefault)) it's working fine, What do you think is the problem here? Thanks!

LEVIS OGCPAS
  • 229
  • 4
  • 11

2 Answers2

3

If I may suggest an alternative? Why don't you have a dictionary:

var avatars = [String: JSQMessagesAvatarImage]()

let storage = FIRStorage.storage()

And use the following function:

override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource!
{
    let message = messages[indexPath.row]
    return avatars[message.senderId]
}

And create the avatars in viewDidLoad (or where ever )

createAvatar(senderId, senderDisplayName: senderDisplayName, user: currentUser,  color: UIColor.lightGrayColor())

with a function

func createAvatar(senderId: String, senderDisplayName: String,  user: FBUser, color: UIColor)
{
    if self.avatars[senderId] == nil
    {
        //as you can see, I use cache
        let img = MyImageCache.sharedCache.objectForKey(senderId) as? UIImage

        if img != nil
        {
            self.avatars[senderId] = JSQMessagesAvatarImageFactory.avatarImageWithImage(img, diameter: 30)

            // print("from cache")
        }
        else if let photoUrl = user.pictureURL where user.pictureURL != ""
        {
       // the images are very small, so the following methods work just fine, no need for Alamofire here

            if photoUrl.containsString("https://firebasestorage.googleapis.com")
            {
                self.storage.referenceForURL(photoUrl).dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
                    if (error != nil)
                    {
                        //deal with error
                    }
                    else
                    {
                        let newImage = UIImage(data: data!)

                        self.avatars[senderId] = JSQMessagesAvatarImageFactory.avatarImageWithImage(newImage, diameter: 30)

                        MyImageCache.sharedCache.setObject(newImage!, forKey: senderId, cost: data!.length)
                    }
                }
            }
            else if let data = NSData(contentsOfURL: NSURL(string:photoUrl)!)
            {
                let newImage = UIImage(data: data)!

                self.avatars[senderId] = JSQMessagesAvatarImageFactory.avatarImageWithImage(newImage, diameter: 30)

                MyImageCache.sharedCache.setObject(newImage, forKey: senderId, cost: data.length)
            }
            else
            {
                //etc. blank image or image with initials
            }
        }
    }
    else
    {
        //etc. blank image or image with initials
    }
}

for Cache I have a custom class

import Foundation

class MyImageCache
{
    static let sharedCache: NSCache =
    {
        let cache = NSCache()
        cache.name = "MyImageCache"
        cache.countLimit = 200 // Max 200 images in memory.
        cache.totalCostLimit = 20*1024*1024 // Max 20MB used.
        return cache
    }()
}

Let me know if that helps

Peter de Vries
  • 780
  • 1
  • 6
  • 14
  • This is an excellent solution and only need to be fired once in the app to give you your avatars. Thus optimizing your app to be quicker on load because you don't have to fetch the image for each cell ever time a cell is created. – Dan Leonard Jul 23 '16 at 22:50
  • Hi! What type is this `MyImageCache`? Is this AnyObject? Also on this line `func createAvatar(senderId: String, senderDisplayName: String, user: FIRUser, color: UIColor)` I replaced `FBUser` to `FIRUser` and I'm getting this error on this line `else if let photoUrl = user.pictureURL where user.pictureURL != ""` What seems to be the problem here? Thanks! – LEVIS OGCPAS Jul 31 '16 at 03:58
  • updated my answer with custom class I use for cache. also I save data that I get of a user in a user class (hence the user.pictureUR), which you will have to either define yourself or instead just pass on the url instead. – Peter de Vries Jul 31 '16 at 22:24
  • @PeterdeVries I tried your approach (altered a tiny bit to work for my project), and only the current user's avatar loads - everyone else's avatar doesn't show. Here's my implementation: https://pastebin.com/ksHmucTX and I call `createAvatar` in `viewDidLoad` the same way you do. I can't figure this out, any help would be much appreciated! I have a question here on SO open right now: http://stackoverflow.com/questions/44057085/jsqmessagesviewcontroller-how-to-get-senderid-in-viewdidload – KingTim May 18 '17 at 20:58
2

I would suggest trying to isolate your problems. I don't know if the issue is with JSQMessagesAvatarImageFactory I think the issue may be that you do not have the image downloaded by the time the cell needs to be displayed. I would make sure that you are getting something back from fireBase before you try and set it to your avatar. A closure is normally how I do this something like

func getImageForUser(id: String, completiion() -> Void) {

//Add your logic for retrieving from firebase

  let imageFromFirebase = firebaserReference.chiledWithID(id)
  completion(image)
}

Then in your

override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
   var avatarImage = JSQAavatarImage()
   getImageForUser {
       self.avatarImage = JSQMessagesAvatarImageFactory.avatarImageWithPlaceholder(imageFromFirebase, diameter: UInt(kJSQMessagesCollectionViewAvatarSizeDefault))
       self.collectionView.reloadItemAtIndexPath(indexPath)
   }

That way it waits till the response is back and then reloads the cell once it is there.

Let me know if you have any other questions.

Dan Leonard
  • 3,325
  • 1
  • 20
  • 32
  • Hey Daniel, I followed your advice which seems to be pretty good. There is just one thing which is not working out for me. Maybe you can have a look here: https://stackoverflow.com/questions/46228051/jsqavatarimage-doesnt-show-anything-although-uiimage-is-loaded – AlexVilla147 Sep 14 '17 at 20:54