K.Wu
K.Wu

Reputation: 3633

How to improve efficiency of getting thumbnails of photos and displaying them in UICollectionView in Swift

I'm writing a simple custom photo picker in Swift. Look at the GIF and you'll notice what's wrong with it:

enter image description here

When I click the status bar to quickly scroll to top, the pictures are all mixed up because cells are being reused and it takes a short amount of time to re-generate the thumbnails, therefore, this happens. I wonder how I could resolve this issue no matter how fast I scroll? My code for this picker is:

func getThumbnail(asset: PHAsset, targetSize: CGSize, onComplete: @escaping (UIImage?, [AnyHashable: Any]?) -> ()) {
    let manager = PHImageManager.default()
    let option = PHImageRequestOptions()
    option.resizeMode = .fast
    option.isNetworkAccessAllowed = true

    manager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: option) { (thumbnail, info) in
        onComplete(thumbnail, info)
    }
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = ...

    let asset = photos[indexPath.row]    // "photos" is an array of all photos in library
                                         // and its type is "[PHAsset]"

    let photoSize = collectionView.frame.width / 3
    getThumbnail(asset: asset, targetSize: CGSize(width: photoSize, height: photoSize, onComplete: { (image, _) -> Void in
        (cell.viewWithTag(1) as! UIImageView).image = image
        // A cell has a UIImageView, whose size is equal to cell size,
        // whose tag = 1
    }))

    return cell
}

Upvotes: 0

Views: 721

Answers (3)

rob mayoff
rob mayoff

Reputation: 385600

From your code:

let cell = ...

At this point, you don't have the image yet, so you should either set the image view's image to nil or to a placeholder image:

let cell = ...
(cell.viewWithTag(1) as! UIImageView).image = placeholderImage

Also from your code:

getThumbnail(asset: asset, targetSize: CGSize(width: photoSize, height: photoSize, onComplete: { (image, _) -> Void in
    (cell.viewWithTag(1) as! UIImageView).image = image

At this point, cell might not be the correct cell for the original index path (and thus for image) anymore. There might be no cell for the original index path, or there might be a different cell for the index path. You need to ask the collection view for the correct cell:

getThumbnail(asset: asset, targetSize: CGSize(width: photoSize, height: photoSize, onComplete: { (image, _) -> Void in
    guard let cell = collectionView.cellForItem(at: indexPath) else { return }
    (cell.viewWithTag(1) as! UIImageView).image = image

Upvotes: 0

Ajay saini
Ajay saini

Reputation: 2470

i was getting the same problem in my UITableView. when i was using AlamofireImage's

af_setImage(withURL:  URL(string: pictureUrl))

then i use AlamofireImage's

af_setImage(withURL:  URL(string: pictureUrl)!, placeholderImage: UIImage(named: "YourPlaceholder.png")) 

using placeholder image solve the problem.

Upvotes: 0

Mark
Mark

Reputation: 515

In your cellForItemAt in the first line put

cell.imageView.image = nil

this will prevent a dequeued cell from reusing the image which will reduce your flicker. If the image is in memory it gets replaced immediately, no issues.

Just because you are using caching does not mean that all your images are currently in memory. Flickering occurs when you are reading the image to memory, especially over a network and when you need to resize cells (although yours look the same size).

You may want to consider only updating the view ONCE at the end of scrolling, rather than for each cell.

You can check scrollViewWillBeginDragging to turn off updating and scrollViewDidEndDecelerating to do the update if needed.

Upvotes: 1

Related Questions