Search code examples
swiftuicollectionviewuiimageviewuicollectionviewcellsdwebimage

collectionView cell Image change when scrolling - swift - programmatically


I need to load an ImageView inside UIcollectionViewcell using a URL that I pass during initialisation:

func configureCellWith(messageModel : MessageModel){
    
    guard let url = URL(string: messageModel.contentUrl!) else { return }
    
    if url.isURLPhoto(){
        likedImageView.sd_setImage(with: url, placeholderImage: nil)
    }
    else if url.isURLVideo(){
        getThumbnailImageFromVideoUrl(url: url) { (image) in
            self.likedImageView.image = image
    }
}

If url is video I need to load the image in this way using this method:

func getThumbnailImageFromVideoUrl(url: URL, completion: @escaping ((_ image: UIImage?)->Void)) {
    DispatchQueue.global().async {
        let asset = AVAsset(url: url)
        let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
        avAssetImageGenerator.appliesPreferredTrackTransform = true
        let thumnailTime = CMTimeMake(value: 2, timescale: 1)
        do {
            let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumnailTime, actualTime: nil)
            let thumbNailImage = UIImage(cgImage: cgThumbImage)
            DispatchQueue.main.async {
                completion(thumbNailImage)
            }
        } catch {
            print(error.localizedDescription)
            DispatchQueue.main.async {
                completion(nil)
            }
        }
    }
}

As visible I retrieve the initial frame of the video and I load it inside the cell, obviously since it's an asynchronous function it will take some time for loading the image, there's no problem In that.

The problem occurs when I scroll through the collection and I see that some cells display images which don't correspond to the correct ones.

Searching online I found out that I need to clear the image in prepareForReuse of the cell and so I did (both in case the image is loaded through sd_setImage and though getThumbnailImageFromVideoUrl function):

override func prepareForReuse() {
    super.prepareForReuse()
    self.likedImageView.image = UIImage()
    self.likedImageView.image = nil
    self.likedImageView.sd_cancelCurrentImageLoad()
    
}

but I still get images mismatched when scrolling thought the collection view, what could be the problem?


Solution

  • I think the issue is not with images, i guess its with video thumbnail. You generate a thumbnail on background thread synchronously but while setting it back to imageView you never bothered to find if the cell is reused and the image u just created is outdated or not.

    So in your cell

    var  currentModel: MessageModel! = nil //declare a instance variable to hold model
    ... other code
    
    func configureCellWith(messageModel : MessageModel){
        self.currentModel = messageModel //keep a copy of model passed to u as argument
    
        guard let url = URL(string: messageModel.contentUrl!) else { return }
        
        if url.isURLPhoto(){
            likedImageView.sd_setImage(with: url, placeholderImage: nil)
        }
        else if url.isURLVideo(){
            getThumbnailImageFromVideoUrl(url: url) { (image) in
                self.likedImageView.image = image
        }
    }
    

    Finally in getThumbnailImageFromVideoUrl

    func getThumbnailImageFromVideoUrl(url: URL, completion: @escaping ((_ image: UIImage?)->Void)) {
            DispatchQueue.global().async {
                let asset = AVAsset(url: url)
                let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
                avAssetImageGenerator.appliesPreferredTrackTransform = true
                let thumnailTime = CMTimeMake(value: 2, timescale: 1)
                do {
                    let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumnailTime, actualTime: nil)
                    let thumbNailImage = UIImage(cgImage: cgThumbImage)
                    if url.absoluteString == currentModel.contentUrl { //check if image you generated is still valid or its no longer needed
                        DispatchQueue.main.async {
                            completion(thumbNailImage)
                        }
                    }
                } catch {
                    print(error.localizedDescription)
                    DispatchQueue.main.async {
                        completion(nil)
                    }
                }
            }