Caching Images in Collectionviewcell in Swift

Swift 3: Caching images in a collectionView

Rather than getting the cell via the cellForItem(at: indexPath) method, just use the cell variable you use to set the image in the cache hit. This will create a strong reference to the cell's image view.

DispatchQueue.main.async(execute: { [weak collectionView] in
cell.image.image = image
collectionView?.reloadData()
})

Consider renaming your UIImageView to imageView rather than image.

Custom UICollectionViewCell not loading from cache

There are multiple issues at play here, but I believe only #1 is causing your problem of images not showing at all.

  1. The line when you check that the cell is the same before setting the image: collectionView.cellForItem(at: indexPath) == cell. cellForItem is actually returning nil as it is offscreen. It works when it downloads the image since there is time to bring it on screen, but when pulling the image out of the cache your completionHandler is called immediately, so the cell has not been returned back to the collectionView yet!

    There are multiple solutions to this, but perhaps the simplest is adding a wasCached flag returned to your completionHandler (see code at the end of this answer).

  2. You are actually downloading each image twice: First, using URLSession.shared.downloadTask, then again inside the completionHandler when you fetch the data from the download task's url. The URL you are passing to try? Data(contentsOf: url) is the image URL from the server, not the file URL returned inside the completionHandler.

  3. From Apple's documentation of downloadTask(with:completionHandler:) the URL passed into the completionHandler block you are reading from:

    The location of a temporary file where the server’s response is stored. You must move this file or open it for reading before your completion handler returns. Otherwise, the file is deleted, and the data is lost.

If you do not need disk caching, then to fix #2 and #3, switch to using dataTask(with:completionHandler:) function instead which provides you with the image data already in memory and you can construct the image from that.

func downloadImageFromURL(
_ urlString: String,
completion: ((_ success: Bool,_ image: UIImage?, _ wasCached: Bool) -> Void)?) {

// First, checks for cachedImage
if let cachedImage = cachedImageForURL(urlString) {
print("Found cached image for URL \(urlString)")
completion?(true, cachedImage, true)
} else {
guard let url = URL(string: urlString) else {
completion?(false,nil, false)
return
}
print("downloadImageFromURL \(urlString)")

let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("downloadImageFromURL complete \(urlString)")
if let error = error {
print("Error \(urlString) \(error.localizedDescription)")
} else if let data = data, let image = UIImage(data: data) {
self.cacheImage(image, forURL: url.absoluteString)
DispatchQueue.main.async { completion?(true, image, false) }
}
}
task.resume()
}
}

And its usage:

ImageManager.shared.downloadImageFromURL(withImageURL) { success, image, wasCached in
if success && image != nil {
// checks that the view did not move before setting the image to the cell!
if wasCached || collectionView.cellForItem(at: indexPath) == cell {
cell.imageView.image = image
}
}
}

Image downloading and caching issue

I think the problem is in your response handler, you are setting cache for url you are requesting, not for url from response, I modified your code a little bit, try, hope it will help you

func downloadImage(url: URL, imageView: UIImageView, placeholder: UIImage? = nil, row: Int) {
imageView.image = placeholder
imageView.cacheUrl = url.absoluteString + "\(row)"
if let cachedImage = imageCache.object(forKey: url.absoluteString as NSString) {
imageView.image = cachedImage
} else {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let response = response as? HTTPURLResponse,
let imageData = data,
let image = UIImage(data: imageData),
let cacheKey = response.url?.absoluteString,
let index = self.arrURLs.firstIndex(of: cacheKey)
else { return }
DispatchQueue.main.async {
if cacheKey + "\(index)" != imageView.cacheUrl { return }
imageView.image = image
self.imageCache.setObject(image, forKey: cacheKey as NSString)
}
}.resume()
}
}

And

var associateObjectValue: Int = 0
extension UIImageView {

fileprivate var cacheUrl: String? {
get {
return objc_getAssociatedObject(self, &associateObjectValue) as? String
}
set {
return objc_setAssociatedObject(self, &associateObjectValue, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}

UPDATED:

swift - UICollectionView or UIImage - how to prevent image caching

I never had the chance to understand when the image cache is evicted.

Collection view cell are dequeued and reused. That means than right after a cell goes out of screen is put in a recycle bin, if the cell is need again is taken from that recycle bin and placed into you views hierarchy (this is called flyweight pattern I guess), basically you are saving the instantiation cost.

Thus when you deal with images and your images are not stored elsewhere in memory but just inside the image view, every time a cell is shown and an new image is passed to the image view the old image should be freed.

This could not happen if you store a reference to the old image elsewhere, for instance a cache or an array.

To load image from xcasset you can only use UIImage(name: ...) that means the image will be cached. Even if Apple says that this cache will be evicted in memory pressure situations and never seen this happening "in time" to avoid the app crashing.

What you can do is do not save in the xcasset folder, but anywhere in your project, in this way they will be loaded into your main bundle, you can load images from here by using the common:

let path = NSBundle.mainBundle().pathForResource(<#T##name: String?##String?#>, ofType: <#T##String?#>)
let image = UIImage(contentsOfFile: path!)

Pay attention that path is an optional.

As a general rule, always try to save resources by using image of the same view size in pixel, the occupied space of an image means nothing, just how its compressed, when loaded in memory it takes n_pixel_height * n_pixel_width * n_channels bytes

swift 4, how do I load over 3000~ pictures in collectionView?

Here are some tips to improve performance when scrolling through the photo library:

Don't implement your own image cache or use the default image manager, use your own instance of PHCachingImageManager. You can tell it to start caching images surrounding your scroll position to improve performance.

Don't use synchronous fetching of images. Use the asynchronous methods and update the image view in the completion block, though note that the completion block can fire before the fetch method has even returned, and fire multiple times as you get better quality images out of the library.

Cancel fetches as your cells go off screen.

I haven't worked with the framework for a while, but I wrote up some notes about it here which are hopefully still relevant.



Related Topics



Leave a reply



Submit