jessesquires / JSQMessagesViewController

An elegant messages UI library for iOS
https://www.jessesquires.com/blog/officially-deprecating-jsqmessagesviewcontroller/
Other
11.14k stars 2.82k forks source link

Couldn't load async image with JSQPhotoMediaItem #1482

Closed macbaszii closed 8 years ago

macbaszii commented 8 years ago

I try to load image from URL with this code, but image view still not showing the image. I'm not sure, I'm on the right way to show an image from url

this is my code which is adding message to the messages array I used Kingfisher to load image

        if newMessage.isMediaMessage() {
            let mediaItem = JSQPhotoMediaItem(image: UIImage.placeholderImage())
            let outGoing = newMessage.senderDisplayName() == viewModel.userDisplayName
            mediaItem.appliesMediaViewMaskAsOutgoing = outGoing
            let message = JSQMessage(senderId: newMessage.senderId(), senderDisplayName: newMessage.senderDisplayName(), date: NSDate(timeIntervalSince1970: Double(newMessage.timeStamp ?? 0)), media: mediaItem)

            if let messageContent = newMessage.content,
                let imageURL = messageContent.first {
                    KingfisherManager.sharedManager.downloader.downloadImageWithURL(NSURL(string: imageURL)!, options: [], progressBlock: nil, completionHandler: { (image, error, imageURL, originalData) -> () in

                        mediaItem.image = image

                        dispatch_async(dispatch_get_main_queue(), { () -> Void in
                            self.collectionView!.reloadData()
                        })
                    })
            }

            viewModel.messages.append(message)
            finishReceivingMessageAnimated(true)
        } else {
            viewModel.messages.append(newMessage)
            finishReceivingMessageAnimated(true)
        }

screen shot 2559-03-05 at 10 07 53 pm

sebastian-zarzycki-apzumi commented 8 years ago

This is poorly documented, but eventually I've figured out that after assigning image, you have to call finishReceivingMessage(). You're doing that right after appending message, and that's ok, but you have to apparently call it again after assigning image.

jessesquires commented 8 years ago

@sebastian-zarzycki-es - this isn't quite correct, but has the same affect.

what you need to do is reload the specific item/section with the media if it is not available immediately.

sebastian-zarzycki-apzumi commented 8 years ago

I've found no way to do just that (reloading a specific item).

jessesquires commented 8 years ago

UICollectionView has APIs for this. :smile:

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/

BhavinBhadani commented 8 years ago

@macbaszii same problem here .. did you find anything????

macbaszii commented 8 years ago

@BhavinBhadani Did you forget to override method call mediaView and return the image view ? It is defined on JSQMediaItem Protocol ?

BhavinBhadani commented 8 years ago

@macbaszii this is what I have done it ...

       JSQPhotoMediaItem *photoItem = [[JSQPhotoMediaItem alloc] initWithImage:img.image];
        JSQMessage *photoMessage = [JSQMessage messageWithSenderId:self.senderId
                                                       displayName:self.senderDisplayName
                                                             media:photoItem];
        [messages addObject:photoMessage];
        [JSQSystemSoundPlayer jsq_playMessageSentSound];

        [self finishSendingMessageAnimated:YES];
macbaszii commented 8 years ago

@BhavinBhadani Have you written this ?

    override func mediaView() -> UIView! {
        return imageView
    }

    override func mediaViewDisplaySize() -> CGSize {
        return imageView.frame.size
    }
BhavinBhadani commented 8 years ago

@macbaszii no ... let me try it ... thanx

macbaszii commented 8 years ago

This is my class if you wanted

import Foundation
import JSQMessagesViewController
import Kingfisher

class AsyncPhotoMediaItem: JSQPhotoMediaItem {
    var asyncImageView: UIImageView!

    override init!(maskAsOutgoing: Bool) {
        super.init(maskAsOutgoing: maskAsOutgoing)
    }

    init(withURL url: NSURL, imageSize: CGSize, isOperator: Bool) {
        super.init()
        appliesMediaViewMaskAsOutgoing = (isOperator == false)
        var size = (imageSize == CGSizeZero) ? super.mediaViewDisplaySize() : ImageType(withSize: imageSize).frameSize()
        let resizedImageSize = UbikHelper.resizeFrameWithSize(imageSize, targetSize: size)
        size.width = min(size.width, resizedImageSize.width)
        size.height = min(size.height, resizedImageSize.height)

        asyncImageView = UIImageView()
        asyncImageView.frame = CGRectMake(0, 0, size.width, size.height)
        asyncImageView.contentMode = .ScaleAspectFit
        asyncImageView.clipsToBounds = true
        asyncImageView.layer.cornerRadius = 20
        asyncImageView.backgroundColor = UIColor.jsq_messageBubbleLightGrayColor()

        let activityIndicator = JSQMessagesMediaPlaceholderView.viewWithActivityIndicator()
        activityIndicator.frame = asyncImageView.frame
        asyncImageView.addSubview(activityIndicator)

        KingfisherManager.sharedManager.cache.retrieveImageForKey(url.hashString(), options: nil) { (image, cacheType) -> () in

            if let image = image {
                self.asyncImageView.image = image
                activityIndicator.removeFromSuperview()
            } else {
                KingfisherManager.sharedManager.downloader.downloadImageWithURL(url, progressBlock: nil) { (image, error, imageURL, originalData) -> () in

                    if let image = image {
                        self.asyncImageView.image = image
                        activityIndicator.removeFromSuperview()

                        KingfisherManager.sharedManager.cache.storeImage(image, forKey: url.hashString(), toDisk: true, completionHandler: nil)
                    }
                }
            }
        }
    }

    override func mediaView() -> UIView! {
        return asyncImageView
    }

    override func mediaViewDisplaySize() -> CGSize {
        return asyncImageView.frame.size
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
BhavinBhadani commented 8 years ago

@macbaszii the problem is in setting avatar pf user ... it also referenced UIImage ...so how to set image with url there ...

- (id<JSQMessageAvatarImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath{
JSQMessage *message = [arrChatMessage objectAtIndex:indexPath.item];
UIImageView *img = [[UIImageView alloc]init];
[img setImageWithURL:[NSURL URLWithString:[dict_Avatars valueForKey:message.senderId.lowercaseString]] placeholderImage:[UIImage imageNamed:@"icon-user"]];
JSQMessagesAvatarImage *imgAvatar = [JSQMessagesAvatarImageFactory avatarImageWithImage:img.image diameter:kJSQMessagesCollectionViewAvatarSizeDefault];
return imgAvatar;
}

thanx for your time

maksumon commented 8 years ago

@BhavinBhadani

Declare @property (strong, nonatomic) JSQMessagesAvatarImage *userAvatarImage;

Download the image with following code block in viewDidLoad

[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:avatarImageURL] options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
   if (!error) {
       self.userAvatarImage = [JSQMessagesAvatarImageFactory avatarImageWithImage:image diameter:kJSQMessagesCollectionViewAvatarSizeDefault];
    }
}];

and return the avatar in

- (id<JSQMessageAvatarImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
{
    JSQMessage *message = [self.chatData.messages objectAtIndex:indexPath.item];

    if ([message.senderId isEqualToString:userInfo.id]) {
        return self.selfAvatarImage;
    } else {
        return self.userAvatarImage;
    }
}
maksumon commented 8 years ago

If anyone would like to have the class in Objective-C that @macbaszii develop in Swift:

#import "JSQPhotoMediaItem.h"

@interface ChatAsyncPhoto : JSQPhotoMediaItem

@property (nonatomic, strong) UIImageView *asyncImageView;

- (instancetype)initWithURL:(NSURL *)URL;

@end
#import "ChatAsyncPhoto.h"
#import "UIColor+JSQMessages.h"
#import "JSQMessagesMediaPlaceholderView.h"
#import "UIImageView+WebCache.h"

@implementation ChatAsyncPhoto

- (instancetype)init
{
    return [self initWithMaskAsOutgoing:YES];
}

- (instancetype)initWithURL:(NSURL *)URL {
    self = [super init];
    if (self) {
        CGSize size = [self mediaViewDisplaySize];

        self.asyncImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
        self.asyncImageView.contentMode = UIViewContentModeScaleToFill;
        self.asyncImageView.clipsToBounds = YES;
        self.asyncImageView.layer.cornerRadius = 20;
        self.asyncImageView.backgroundColor = [UIColor jsq_messageBubbleLightGrayColor];

        UIView *activityIndicator = [JSQMessagesMediaPlaceholderView viewWithActivityIndicator];
        activityIndicator.frame = self.asyncImageView.frame;

        [self.asyncImageView addSubview:activityIndicator];

        UIImage *image = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:URL.absoluteString];
        if(image == nil)
        {
            [self.asyncImageView sd_setImageWithURL:URL completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                if (error == nil) {
                    [self.asyncImageView setImage:image];
                    [activityIndicator removeFromSuperview];
                } else {
                    NSLog(@"Image downloading error: %@", [error localizedDescription]);
                }
            }];
        } else {
            [self.asyncImageView setImage:image];
            [activityIndicator removeFromSuperview];
        }
    }

    return self;
}

#pragma mark - JSQMessageMediaData protocol
- (UIView *)mediaView
{
    return self.asyncImageView;
}

#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    NSLog(@"init coder has not been implemented");

    return self;
}

@end

Then you can use the class as follows:

ChatAsyncPhoto *photoItem = [[ChatAsyncPhoto alloc] initWithURL:[NSURL URLWithString:messageItem.content]];
JSQMessage *message = [[JSQMessage alloc] initWithSenderId:messageItem.userInfo.id
                                         senderDisplayName:messageItem.userInfo.username
                                                      date:[DateUtil getTimeDateStringFromDate:messageItem.createdDate]
                                                     media:photoItem];
[self.chatData.messages addObject:message];
MHX792 commented 8 years ago

I use a combined slim version of @macbaszii and @maksumon's code (thanks for sharing!), but the media bubble is not in line with the regular text bubbles and I have no idea, how to move it on the x axis:

screen shot 2016-08-01 at 20 43 14

My code:

import Foundation
import JSQMessagesViewController
import Kingfisher

class AsyncPhotoMediaItem: JSQPhotoMediaItem {
    var asyncImageView: UIImageView!

    override init!(maskAsOutgoing: Bool) {
        super.init(maskAsOutgoing: maskAsOutgoing)
    }

    init(withURL url: NSURL) {
        super.init()
        asyncImageView = UIImageView()
        asyncImageView.frame = CGRectMake(0, 0, 170, 130)
        asyncImageView.contentMode = .ScaleAspectFill
        asyncImageView.clipsToBounds = true
        asyncImageView.layer.cornerRadius = 20
        asyncImageView.backgroundColor = UIColor.jsq_messageBubbleLightGrayColor()

        let activityIndicator = JSQMessagesMediaPlaceholderView.viewWithActivityIndicator()
        activityIndicator.frame = asyncImageView.frame
        asyncImageView.addSubview(activityIndicator)

        KingfisherManager.sharedManager.cache.retrieveImageForKey(url.absoluteString, options: nil) { (image, cacheType) -> () in

            if let image = image {
                self.asyncImageView.image = image
                activityIndicator.removeFromSuperview()
            } else {
                KingfisherManager.sharedManager.downloader.downloadImageWithURL(url, progressBlock: nil) { (image, error, imageURL, originalData) -> () in

                    if let image = image {
                        self.asyncImageView.image = image
                        activityIndicator.removeFromSuperview()

                        KingfisherManager.sharedManager.cache.storeImage(image, forKey: url.absoluteString, toDisk: true, completionHandler: nil)
                    }
                }
            }
        }
    }

    override func mediaView() -> UIView! {
        return asyncImageView
    }

    override func mediaViewDisplaySize() -> CGSize {
        return asyncImageView.frame.size
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
eliburke commented 8 years ago

If you are using that swizzle code from the tail/tailless bubble issue, look no further than the insets: return [self initWithBubbleImage:[UIImage imageNamed:@"chat_bubble_tailless"] capInsets:UIEdgeInsetsZero];

There are other insets available in lots of places to tweak the layout. For example: self.collectionView.collectionViewLayout.messageBubbleTextViewTextContainerInsets self.collectionView.collectionViewLayout.messageBubbleTextViewFrameInsets

You can also adjust the bubble insets directly for any of the text bubbles created by the JSQMessagesBubbleImageFactory returned from: - (id<JSQMessageBubbleImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath

And you can adjust the text insets directly in your cellForItem and willDisplayCell methods:

- (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath];
    if (cell.messageBubbleTopLabel.textInsets.left) {
        cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0, 2 + cell.avatarImageView.image.size.width, 2, 0);
    }

This is of course all objective C. Translation to swift is left as an exercise for the reader 😄

MHX792 commented 8 years ago

Hi, thanks for all the hints, but none of them seems to work. Using UIEdgeInsetsMake doesn't make any difference. It seems to have something to do with the asyncImageView. If I comment it out everything is allright:

screen shot 2016-08-01 at 22 36 48

Interestingly this also has no effect:

override func mediaView() -> UIView! {
        self.asyncImageView.center.x += 30
        return asyncImageView
    }

Edit:

This what it looks like when the corner radius is disabled. No matter what I try the gray box is not moving. Chaning frame or bounds only moves the loading indicator.

screen shot 2016-08-01 at 23 18 17

MHX792 commented 8 years ago

Any help is appreciated.

Update: This workaround has fixed it for me:

    override func mediaView() -> UIView! {
        let view = UIView()
        view.addSubview(asyncImageView)
        asyncImageView.frame.origin.x = self.appliesMediaViewMaskAsOutgoing ? -6 : 6
        return view
    }

I am still interested in the non-hacky solution though. :)

anhphideptrai commented 8 years ago
screen shot 2016-09-09 at 9 38 59 pm

import UIKit import JSQMessagesViewController.JSQMessages import AlamofireImage

class JSQPhotoMediaItemCustom: JSQPhotoMediaItem {

var imgView : UIImageView!
var uRL     :NSURL?

override init!(maskAsOutgoing: Bool) {
    super.init(maskAsOutgoing: maskAsOutgoing)
}

init(withURL url: NSURL, isOperator: Bool) {
    super.init()
    uRL                             = url
    appliesMediaViewMaskAsOutgoing  = (isOperator == true)
    let size                        = super.mediaViewDisplaySize()
    imgView                         = UIImageView()
    imgView.frame                   = CGRectMake(0, 0, size.width, size.height)
    imgView.contentMode             = .ScaleAspectFill
    imgView.clipsToBounds           = true
    imgView.backgroundColor         = UIColor.jsq_messageBubbleLightGrayColor()

    let activityIndicator           = JSQMessagesMediaPlaceholderView.viewWithActivityIndicator()
    activityIndicator.frame         = imgView.frame
    imgView.addSubview(activityIndicator)

    let filter = AspectScaledToFillSizeWithRoundedCornersFilter(
        size: size,
        radius: 0
    )

    JSQMessagesMediaViewBubbleImageMasker.applyBubbleImageMaskToMediaView(self.imgView, isOutgoing: self.appliesMediaViewMaskAsOutgoing)

    imgView.af_setImageWithURL(url, filter: filter) { (
        response ) in
        let image = response.result.value
        if  image != nil{

            activityIndicator.removeFromSuperview()
            self.imgView.image = image
        }
    }
}

override func mediaView() -> UIView! {
    return imgView
}

override func mediaViewDisplaySize() -> CGSize {
    return imgView.frame.size
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

}

indrajitsinh commented 7 years ago

@macbaszii code updated for Swift 3

Swift 3 Update

//
//  AsyncPhotoMediaItem.swift
//  *******
//
//  Created by IndrajitSinh Rayjada on 16/11/16.
//  Copyright © 2016 ******. All rights reserved.
//

import Foundation
import JSQMessagesViewController
import Kingfisher

class AsyncPhotoMediaItem: JSQPhotoMediaItem {
    var asyncImageView: UIImageView!

    override init!(maskAsOutgoing: Bool) {
        super.init(maskAsOutgoing: maskAsOutgoing)
    }

    init(withURL url: URL, imageSize: CGSize, isOperator: Bool) {
        super.init()
        appliesMediaViewMaskAsOutgoing = (isOperator == false)
        ImageType(withSize: imageSize).frameSize()
        asyncImageView = UIImageView()
        asyncImageView.frame = CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
        asyncImageView.contentMode = .scaleAspectFit
        asyncImageView.clipsToBounds = true
        asyncImageView.layer.cornerRadius = 20
        asyncImageView.backgroundColor = UIColor.jsq_messageBubbleLightGray()

        let activityIndicator = JSQMessagesMediaPlaceholderView.withActivityIndicator()
        activityIndicator?.frame = asyncImageView.frame
        asyncImageView.addSubview(activityIndicator!)

        KingfisherManager.shared.cache.retrieveImage(forKey: url.absoluteString, options: nil) { (image, cacheType) -> () in

            if let image = image {
                self.asyncImageView.image = image
                activityIndicator?.removeFromSuperview()
            } else {

                 KingfisherManager.shared.downloader.downloadImage(with: url , progressBlock: nil) { (image, error, imageURL, originalData) -> () in

                    if let image = image {
                        self.asyncImageView.image = image
                        activityIndicator?.removeFromSuperview()
                        KingfisherManager.shared.cache.store(image, forKey: url.absoluteString)

                    }
                }
            }
        }
    }

    override func mediaView() -> UIView! {
        return asyncImageView
    }

    override func mediaViewDisplaySize() -> CGSize {
        return asyncImageView.frame.size
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
candotti commented 7 years ago

To avoid to set null in dict here I tried to set self.image after [self.asyncImageView setImage]. It seems to work Tested on @macbaszii code

Isuru-Nanayakkara commented 7 years ago

@indrajitsinh @macbaszii Where's this ImageType (ImageType(withSize: imageSize).frameSize()) object from?

nothingimportant55 commented 7 years ago

as @Isuru-Nanayakkara .. where is ImageType from?

macbaszii commented 7 years ago

@Isuru-Nanayakkara @nothingimportant55 It's my internal helper to determine that the image is vertical or horizontal. For my case, I have to do it in my backend side.

For you case, you can return any size that you'd like it to be or return super.mediaViewDisplaySize() for default size you've set below.

Sorry for there is no comment for describing this.

rlee1990 commented 7 years ago

Can anyone tell me what I am doing wrong? I am following what @MHX792 is using. When I send an image the user has to back out of the message and go back in for the image to show. If not it will just keep showing the activity indicator.

thongtran715 commented 7 years ago

Hey guys I am totally new IOS developer. How do I use @macbaszii code ?

rlee1990 commented 7 years ago

@thongtran715 I would say find one of the youtube videos that walk you through using this. It help me understand it better.

thongtran715 commented 7 years ago

Did you find anything on Youtube @rlee1990 . I personally could not find one

rlee1990 commented 7 years ago

@thongtran715 How to Build a Whatsapp Clone App with Firebase 3 and JSQMessageViewController in Swift 2.2: https://www.youtube.com/playlist?list=PLM-WD6b2B_nJoIkl7SfFiBrloNJ3zfU6n

rlee1990 commented 7 years ago

Its easy to follow for and 3.0 and up also

thongtran715 commented 7 years ago

Thanks man. I will take sometime watching these videos

rlee1990 commented 7 years ago

Welcome

thongtran715 commented 7 years ago

Hey Man I have my code here. I am facing the problem exact the same above. How can I fix this? func observeMessage () {

    guard let uid = FIRAuth.auth()?.currentUser?.uid else {
        return
    }

    let ref = FIRDatabase.database().reference().child("users-messages").child(uid).child(toId!)
    ref.observe(.childAdded, with: { (snapshot) in
        let messageId = snapshot.key
        let messageRef = FIRDatabase.database().reference().child("Messages").child(messageId)
        messageRef.observeSingleEvent(of: .value, with: { (snapshot) in

            if let dictionary = snapshot.value as? [String: AnyObject] {
                let message = Message()
                message.fromId = dictionary["fromId"] as? String
                message.text = dictionary["text"] as? String
                message.timeStamp = dictionary["timeStamp"] as? Int
                message.toId = dictionary["toId"] as? String
                message.imageURL = dictionary["ImageURL"] as? String
                    let id = FIRAuth.auth()?.currentUser?.uid

                if let text = message.text {
                    if message.fromId == id {
                        let messageJQ = JSQMessage(senderId: "1", displayName: self.user.full_name, text: text)
                        self.messages.append(messageJQ!)
                    }
                    else
                    {
                        let messageJQ = JSQMessage(senderId: "2", displayName: self.tutor_to_chat?.full_name, text: text)
                        self.messages.append(messageJQ!)
                    }
                }
                else if message.imageURL != nil {
                   let imageURL = message.imageURL!
                    let imageView = UIImageView()
                    DispatchQueue.main.async {

                    imageView.loadImageFromURl(url_name: imageURL)
                    let image = JSQPhotoMediaItem(image: imageView.image)

                    if message.fromId == id {
                            let messageJQ = JSQMessage(senderId: "1", displayName: self.user.full_name, media: image)
                            self.messages.append(messageJQ!)
                    }
                    else
                    {
                          let messageJQ = JSQMessage(senderId: "2", displayName: self.tutor_to_chat?.full_name, media: image)
                            self.messages.append(messageJQ!)
                    }

                }
                }

                            self.collectionView.reloadData()

            }
        }, withCancel: nil)
    }, withCancel: nil)

}
rlee1990 commented 7 years ago

@thongtran715 try this: ` let imageView = AsyncPhotoMediaItem(withURL: URL(string: mediaUrl)!)

            if senderId == Auth.auth().currentUser!.uid {
                imageView.appliesMediaViewMaskAsOutgoing = true
            } else {
                imageView.appliesMediaViewMaskAsOutgoing = false
            }
            self.messages.append(JSQMessage(senderId: senderId, senderDisplayName: displayName, date: timeStamp, media: imageView));`
rlee1990 commented 7 years ago

to get that i used this `import Foundation import JSQMessagesViewController import Kingfisher

class AsyncPhotoMediaItem: JSQPhotoMediaItem { var asyncImageView: UIImageView!

override init!(maskAsOutgoing: Bool) {
    super.init(maskAsOutgoing: maskAsOutgoing)
}

init(withURL url: URL) {
    super.init()
    asyncImageView = UIImageView()
    asyncImageView.frame = CGRect(x: 0, y: 0, width: 170, height: 130)
    asyncImageView.contentMode = .scaleAspectFill
    asyncImageView.clipsToBounds = true
    asyncImageView.layer.cornerRadius = 20
    asyncImageView.backgroundColor = UIColor.jsq_messageBubbleLightGray()

    let activityIndicator = JSQMessagesMediaPlaceholderView.withActivityIndicator()
    activityIndicator?.frame = asyncImageView.frame
    asyncImageView.addSubview(activityIndicator!)

    KingfisherManager.shared.cache.retrieveImage(forKey: url.absoluteString, options: nil) { (image, cacheType) -> () in

        if let image = image {
            self.asyncImageView.image = image
            activityIndicator?.removeFromSuperview()
        } else {

            KingfisherManager.shared.downloader.downloadImage(with: url , progressBlock: nil) { (image, error, imageURL, originalData) -> () in

                if let image = image {
                    self.asyncImageView.image = image
                    activityIndicator?.removeFromSuperview()
                    KingfisherManager.shared.cache.store(image, forKey: url.absoluteString)

                }
            }
        }
    }
}

override func mediaView() -> UIView! {
    return asyncImageView
}

override func mediaViewDisplaySize() -> CGSize {
    return asyncImageView.frame.size
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

} `

thongtran715 commented 7 years ago

HI @rlee1990 I tried to install pod Kingfisher, however xcode asks me to change to current version swift 3.0 which shows me the error. I do not if I install it correctly here is what I did pod 'Kingfisher', '~> 1.2'

rlee1990 commented 7 years ago

it should be pod 'Kingfisher', '~> 3.0'

thongtran715 commented 7 years ago

I am so sorry for bothering you, but I am complete new ios developer. I got the message error here when I install kingfisher 3.0 [!] Unable to satisfy the following requirements:

None of your spec sources contain a spec satisfying the dependency: Kingfisher (~> 3.0).

You have either:

Note: as of C/ocoaPods 1.0, pod repo update does not happen on pod install by default. How do I fix it

rlee1990 commented 7 years ago

Have you tried the guide? https://github.com/onevcat/Kingfisher

thongtran715 commented 7 years ago

Holy Mama and Buddha I got the problem fixed. There is no words to say thank you enough @rlee1990

rlee1990 commented 7 years ago

@thongtran715 glad I could help. We all have been there.

thongtran715 commented 7 years ago

Thank you so so much

rlee1990 commented 7 years ago

Welcome

thongtran715 commented 7 years ago

Hey @rlee1990 . I got an error or crashed like this. I opened the chat log on my phone, I locked it and I unlocked the phone again. The app was crashed, the error said that image != nil. I do not know why I have that problem Here is my code // // ChatLogController.swift // Find-Tutor // // Created by Thong Tran on 7/30/17. // Copyright © 2017 ThongApp. All rights reserved. //

import UIKit import Firebase import JSQMessagesViewController import SDWebImage import AVKit

class ChatLogController: JSQMessagesViewController{

var user = User()
var timer : Timer?
func fetch_current_user_name () ->String {
    return user.full_name!
}
// Variables
var tutor_to_chat: User?
var toId : String?
// all messages of users1, users2
var messages = [JSQMessage]()

}

extension ChatLogController { func handle_photo_picture() { let picker = UIImagePickerController() picker.delegate = self picker.allowsEditing = true present(picker, animated: true, completion: nil) }

override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {

    handleMessage(text: text, toId: toId!, fromId: (FIRAuth.auth()?.currentUser?.uid)!)

    finishSendingMessage()
}
override func didPressAccessoryButton(_ sender: UIButton!) {

    let alert = UIAlertController(title: nil, message: "Tap Photos to send Photo", preferredStyle: .actionSheet)
    let photo = UIAlertAction(title: "Photos", style: .default) { (action) in

        self.handle_photo_picture()

    }
    let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    //let videos = UIAlertAction(title: "Videos", style: .default) { (action) in
    //}

    alert.addAction(photo)
    // alert.addAction(videos)
    alert.addAction(cancel)
    present(alert, animated: true, completion: nil)
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath!) -> NSAttributedString! {
    let message = messages[indexPath.row]
    let messageUsersname = message.senderDisplayName

    return NSAttributedString(string: messageUsersname!)
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) {
    let msg = messages[indexPath.item];
    if msg.isMediaMessage {
        if let mediaItem = msg.media as? JSQVideoMediaItem {
            let player = AVPlayer(url: mediaItem.fileURL);
            let playerController = AVPlayerViewController();
            playerController.player = player;
            self.present(playerController, animated: true, completion: nil);
        }
    }
    else {
        self.performZoom()
    }
}
func performZoom() {
    print(123)
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat {
    return 15
}

override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
    let imageView = UIImageView()
    var profile_image : String?
    if "1" == messages[indexPath.row].senderId {
        profile_image = self.user.profileImageUrl
    }
    else
    {
        profile_image = self.tutor_to_chat?.profileImageUrl
    }
    imageView.loadImageFromURl(url_name: profile_image!)

    let avatarImage = JSQMessagesAvatarImageFactory.avatarImage(with: imageView.image, diameter: UInt(kJSQMessagesCollectionViewAvatarSizeDefault))
    return avatarImage
}

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
    let bubbleFactory = JSQMessagesBubbleImageFactory()

    let message = messages[indexPath.row]

    if "1" == message.senderId {
        return bubbleFactory?.outgoingMessagesBubbleImage(with: .red)
    } else {
        return bubbleFactory?.incomingMessagesBubbleImage(with: .green)
    }
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return messages.count
}

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
    return messages[indexPath.row]
}

}

extension ChatLogController { override func viewDidLoad() {

    super.viewDidLoad()
    self.senderId = "1"
    self.senderDisplayName = self.fetch_current_user_name()
    observeMessage()
    self.navigationController?.navigationBar.tintColor = UIColor.red
    self.set_up_user_nav_bar(name: (tutor_to_chat?.full_name)!, profileImageURL: (tutor_to_chat?.profileImageUrl)!)
    finishReceivingMessage()
}

} extension ChatLogController { func handleMessage (text: String, toId: String , fromId: String ) { let ref = FIRDatabase.database().reference().child("Messages") let childUnique = ref.childByAutoId() let timeStamp: Int = Int(NSDate().timeIntervalSince1970) let values = ["text" : text, "toId": toId, "fromId": fromId, "timeStamp": timeStamp ] as [String : Any] childUnique.updateChildValues(values) { (error, ref) in if error != nil { print(error as Any) return } let userMessageRef = FIRDatabase.database().reference().child("users-messages").child(fromId).child(toId) let messageId = childUnique.key userMessageRef.updateChildValues([messageId : 1])

        let receiverMessageRef = FIRDatabase.database().reference().child("users-messages").child(toId).child(fromId)
        receiverMessageRef.updateChildValues([messageId : 1])

    }
}

}

extension ChatLogController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerControllerDidCancel( picker: UIImagePickerController) { self.dismiss(animated: true, completion: nil) } func imagePickerController( picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { var image_temp : UIImage? if let orignialImage = info["UIImagePickerControllerOriginalImage"] as? UIImage { image_temp = orignialImage } else if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage { image_temp = editedImage } if let image = image_temp { //self.profileImage.image = image self.uploadImageToDB(image: image) } self.dismiss(animated: true) { self.finishSendingMessage() } self.collectionView.reloadData() }

func uploadImageToDB ( image : UIImage) {
    let messageName = NSUUID().uuidString
    let storageRef = FIRStorage.storage().reference().child("Messages_Images").child("\(messageName).png")
    if let uploadData = UIImageJPEGRepresentation(image, 0.1) {
        storageRef.put(uploadData, metadata: nil, completion: { (metadata, error) in
            if error != nil {
                print(error?.localizedDescription as Any)
            }
            self.uploadImageMessage(imageURL: (metadata?.downloadURL()?.absoluteString)!, toId: self.toId!, fromId: (FIRAuth.auth()?.currentUser?.uid)!)
        })
    }

}

private func uploadImageMessage (imageURL : String ,toId: String , fromId : String) {
    let ref = FIRDatabase.database().reference().child("Messages")
    let childUnique = ref.childByAutoId()
    let timeStamp: Int = Int(NSDate().timeIntervalSince1970)
    let values = ["ImageURL" : imageURL, "toId": toId, "fromId": fromId, "timeStamp": timeStamp ] as [String : Any]
    childUnique.updateChildValues(values) { (error, ref) in
        if error != nil {
            print(error as Any)
            return
        }
        let userMessageRef = FIRDatabase.database().reference().child("users-messages").child(fromId).child(toId)
        let messageId = childUnique.key
        userMessageRef.updateChildValues([messageId : 1])

        let receiverMessageRef = FIRDatabase.database().reference().child("users-messages").child(toId).child(fromId)
        receiverMessageRef.updateChildValues([messageId : 1])

    }
}

}

// Fetch Users Messsages -OR- Observe Messages extension ChatLogController {

func observeMessage () {

    guard let uid = FIRAuth.auth()?.currentUser?.uid else {
        return
    }

    let ref = FIRDatabase.database().reference().child("users-messages").child(uid).child(toId!)
    ref.observe(.childAdded, with: { (snapshot) in
        let messageId = snapshot.key
        let messageRef = FIRDatabase.database().reference().child("Messages").child(messageId)
        messageRef.observeSingleEvent(of: .value, with: { (snapshot) in

            if let dictionary = snapshot.value as? [String: AnyObject] {
                let message = Message()
                message.fromId = dictionary["fromId"] as? String
                message.text = dictionary["text"] as? String
                message.timeStamp = dictionary["timeStamp"] as? Int
                message.toId = dictionary["toId"] as? String
                message.imageURL = dictionary["ImageURL"] as? String
                let id = FIRAuth.auth()?.currentUser?.uid
                if let text = message.text {
                    if message.fromId == id {
                        let messageJQ = JSQMessage(senderId: "1", displayName: self.user.full_name, text: text)
                        self.messages.append(messageJQ!)
                    }
                    else
                    {
                        let messageJQ = JSQMessage(senderId: "2", displayName: self.tutor_to_chat?.full_name, text: text)
                        self.messages.append(messageJQ!)
                    }
                }
                else if message.imageURL != nil {

                    let imageURL = message.imageURL!
                    let imageView = AsyncPhotoMediaItem(withURL: URL(string: imageURL)!)

                    let date = Date()
                    if message.fromId == id {
                        imageView.appliesMediaViewMaskAsOutgoing = true
                        let messageJQ = JSQMessage(senderId: "1", senderDisplayName: self.user.full_name, date: date ,  media: imageView)
                        self.messages.append(messageJQ!)
                    }
                    else
                    {
                        imageView.appliesMediaViewMaskAsOutgoing = false
                        let messageJQ = JSQMessage(senderId: "2", senderDisplayName: self.tutor_to_chat?.full_name, date: date ,  media: imageView)
                        self.messages.append(messageJQ!)
                    }
                }
                DispatchQueue.main.async {
                    self.collectionView.reloadData()
                    let indexPath = NSIndexPath(item: self.messages.count - 1, section: 0)
                    self.collectionView.scrollToItem(at: indexPath as IndexPath, at: .bottom, animated: true)
                }

            }
        }, withCancel: nil)
    }, withCancel: nil)

}

}

extension ChatLogController { func set_up_user_nav_bar ( name : String , profileImageURL : String ) { let titleView = UIView() titleView.frame = CGRect(x: 0, y: 0, width: 100, height: 40) let containerView = UIView() containerView.translatesAutoresizingMaskIntoConstraints = false titleView.addSubview(containerView)

    let profileImageView = UIImageView()
    profileImageView.translatesAutoresizingMaskIntoConstraints = false
    profileImageView.contentMode = .scaleAspectFill
    profileImageView.layer.cornerRadius = 20
    profileImageView.clipsToBounds = true
    profileImageView.loadImageFromURl(url_name: profileImageURL)
    containerView.addSubview(profileImageView)

    profileImageView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
    profileImageView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
    profileImageView.widthAnchor.constraint(equalToConstant: 40).isActive = true
    profileImageView.heightAnchor.constraint(equalToConstant: 40).isActive = true

    let name_label = UILabel()
    containerView.addSubview(name_label)
    name_label.text = name
    name_label.translatesAutoresizingMaskIntoConstraints = false
    name_label.leftAnchor.constraint(equalTo: profileImageView.rightAnchor, constant: 8).isActive = true
    name_label.centerYAnchor.constraint(equalTo: profileImageView.centerYAnchor).isActive = true
    name_label.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
    name_label.heightAnchor.constraint(equalTo: profileImageView.heightAnchor).isActive = true

    containerView.centerXAnchor.constraint(equalTo: titleView.centerXAnchor).isActive = true
    containerView.centerYAnchor.constraint(equalTo: titleView.centerYAnchor).isActive = true

    self.navigationItem.titleView = titleView
}

}

ahmedAlmasri commented 7 years ago

Dears

my issue

all images align right simulator screen shot aug 25 2017 10 33 03 pm

RabbitNever commented 6 years ago

Hey guys, I'm developing a IM app with JSQMessagesViewController. I want to know how to notify the view controller to reload the correct cell when its media item's content has downloaded. I am confusing on it for several days.