UICollectionView fast scrolling displays wrong images in the cell when new items are appended asynchronously

When the collection view scrolls a cell out of bounds, the collection view may reuse the cell to display a different item. This is why you get cells from a method named dequeueReusableCell(withReuseIdentifier:for:).

You need to make sure, when the image is ready, that the image view is still supposed to display that item's image.

I recommend you change your Nuke.loadImage(with:into:) method to take a closure instead of an image view:

struct Nuke {

    static func loadImage(with request: URLRequest, body: @escaping (UIImage) -> ()) {
        // ...
    }

}

That way, in collectionView(_:cellForItemAt:), you can load the image like this:

Nuke.loadImage(with: request) { [weak collectionView] (image) in
    guard let cell = collectionView?.cellForItem(at: indexPath) as? MyCollectionViewCell
    else { return }
    cell.itemImageView.image = image
}

If the collection view is no longer displaying the item in any cell, the image will be discarded. If the collection view is displaying the item in any cell (even in a different cell), you'll store the image in the correct image view.


Try this:

Whenever the cell is reused, its prepareForReuse method is called. You can reset your cell here. In your case, you can set a default image in the image view here till the original image is downloaded.

 override func prepareForReuse()
    {
        super.prepareForReuse()
        self.imageView.image = UIImage(named: "DefaultImage"
    }

@discardableResult
public func loadImage(with url: URL,
                  options: ImageLoadingOptions = ImageLoadingOptions.shared,
                  into view: ImageDisplayingView,
                  progress: ImageTask.ProgressHandler? = nil,
                  completion: ImageTask.Completion? = nil) -> ImageTask? {
return loadImage(with: ImageRequest(url: url), options: options, into: view, progress: progress, completion: completion)
}

all Nuke API's return an ImageTask when requesting unless the image was in the cache. Hold reference to this ImageTask if there is one. In the prepareForReuse function. call ImageTask.cancel() in it and set the imageTask to nil.