How to asynchronous load image from a web-server in UICollectionView using NSCache

Hello,

I have some issues when loading images from a web-server in UICollectionView using NScache.


The problem:

The images are not proper displayed:

1. sometimes they are not showned in the corresponding cell

or

2. the image is changing on scroll


Situation:

1. I have 3 arrays whitch are properly loaded from the web-server in function viewDidLoad(). These arrays are: vPrice, vTitle and vImages_api

2. my custom class for cell have:

- label for price: cell.lblPrice

- label for title: cell.lblTitle

- image: cell.imgPicture


I belive that the problem is in function func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell

It could be related either to the way I use NSCache or to the way I use and when I use DispatchQueue.



The code:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

let cell = self.collectionViewPRODUSE.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! clsCustomCell



cell.lblPrice.text = vPrice[indexPath.item]

cell.lblTitle.text = vTitle[indexPath.item]

cell.layer.borderColor = UIColor.lightGray.cgColor

cell.layer.borderWidth = 0.5



DispatchQueue.global(qos: .userInitiated).async {

//background thread

let ImageString = self.vImages_api[indexPath.item]

let imageUrl = URL(string: ImageString)

let imageData = NSData(contentsOf: imageUrl!)

// main thread to update the UI

DispatchQueue.main.async {

let key1 = self.vImages_api[indexPath.item] as AnyObject

//if i saved allready my image in cache, then i will load my image from cache

if let imageFromCache = self.objCache.object(forKey: key1){

cell.imgPicture.image = imageFromCache as! UIImage

}

else{//if my image is not in cache ......

if imageData != nil {

let myPicture = UIImage(data: imageData! as Data)

cell.imgPicture.image = myPicture

//save my image in cache

self.objCache.setObject(myPicture!, forKey: ImageString as AnyObject)

}

}

}

}


return cell

}

The problems you are seeing are a result of the way Collection Views work and the interaction of several asynchronous tasks. Collection Views re-use cells as you scroll. So if the cell for index path 0,0 scrolls off the screen, and now the cell for index path 0,7 is needed, the cell that is retrieved for path 0,7 may be the same one that was displayed at 0,0. You will see that image until your code to assign the image for 0,7 completes. This also means that by the time you retrieve an image, the path where it belongs may no longer refer to a visible cell. You should probably be using prefetching (see "Prefetching Collection View Cells and Data" section here including following the links), but you still have to handle the case where the Collection View asks the data source for a cell that you don't have the image for yet. One possible strategy is to avoid referencing the local cell variable inside your dispatched code that loads the image. Instead, just keep track of the path where it should be displayed, then when you have the image check if the cell at that path is still visible before you try to set the image. Something like this:

if let visibleCell = self.collectionViewPRODUSE.cellForItem(at: prefetchPath) as! clsCustomCustomCell? { // returns nil if not visible
    visibleCell.imgPicture.image = imageFromCache
}

Also, set the image propery to nil right after you dequeue a cell so you won't see a previous image while you are waiting for a new image to finish loading.

How to asynchronous load image from a web-server in UICollectionView using NSCache
 
 
Q