iosswiftimage-caching

Image downloading and caching issue


I am downloading images from server and showing it in collectionView. I am caching the Images so that user got fast server response and no glitches in UI. Until the image is not downloaded I added placeholder image too.

But in my output, the image is replicating in other cells and images is not caching in NSCache properly..

Here is the below code

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    @IBOutlet weak var colView: UICollectionView!

    var imageCache = NSCache<NSString, UIImage>()
    var arrURLs = [
        "https://homepages.cae.wisc.edu/~ece533/images/airplane.png",
        "https://homepages.cae.wisc.edu/~ece533/images/arctichare.png",
        "https://homepages.cae.wisc.edu/~ece533/images/baboon.png",
        "https://homepages.cae.wisc.edu/~ece533/images/barbara.png",
        "https://homepages.cae.wisc.edu/~ece533/images/boat.png",
        "https://homepages.cae.wisc.edu/~ece533/images/cat.png",
        "https://homepages.cae.wisc.edu/~ece533/images/fruits.png",
        "https://homepages.cae.wisc.edu/~ece533/images/frymire.png",
        "https://homepages.cae.wisc.edu/~ece533/images/girl.png",
        "https://homepages.cae.wisc.edu/~ece533/images/goldhill.png",
        "https://homepages.cae.wisc.edu/~ece533/images/lena.png",
        "https://homepages.cae.wisc.edu/~ece533/images/monarch.png",
        "https://homepages.cae.wisc.edu/~ece533/images/mountain.png",
        "https://homepages.cae.wisc.edu/~ece533/images/peppers.png",
        "https://homepages.cae.wisc.edu/~ece533/images/pool.png",
        "https://homepages.cae.wisc.edu/~ece533/images/sails.png",
        "https://homepages.cae.wisc.edu/~ece533/images/serrano.png",
        "https://homepages.cae.wisc.edu/~ece533/images/tulips.png",
        "https://homepages.cae.wisc.edu/~ece533/images/watch.png",
        "https://homepages.cae.wisc.edu/~ece533/images/zelda.png"
    ]


func downloadImage(url: URL, imageView: UIImageView, placeholder : UIImage) {

    imageView.image = placeholder // Set default placeholder..

    // Image is set if cache is available
    if let cachedImage = imageCache.object(forKey: url.absoluteString as NSString) {
        imageView.image = cachedImage
    } else {
        // Reset the image to placeholder as the URLSession fetches the new image
        imageView.image = placeholder
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard error == nil else  {
                // You should be giving an option to retry the image here
                imageView.image = placeholder
                return
            }

            if let respo  = response as? HTTPURLResponse {

                print("Status Code : ", respo.statusCode)

                if let imageData = data, let image = UIImage(data: imageData) {
                    self.imageCache.setObject(image, forKey: url.absoluteString as NSString)
                    // Update the imageview with new data
                    DispatchQueue.main.async {
                        imageView.image = image
                    }
                } else {
                    // You should be giving an option to retry the image here
                    imageView.image = placeholder
                }
            }
            }.resume()
    }
}


    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let w = self.view.bounds.width - 30

        return CGSize(width: w, height: w + 60)
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return arrURLs.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DummyCollectionViewCell", for: indexPath) as! DummyCollectionViewCell

        let str = arrURLs[indexPath.item]
        let url = URL(string: str)

        downloadImage(url: url!) { (img) in
            DispatchQueue.main.async {
                cell.imgView.image = img ?? UIImage(named: "placeholder")
            }
        }

        return cell
    }
}

Output GIF

enter image description here


Due to size restriction on stack, the above gif is in low quality. If you need to check gif in full size then please refer : https://i.sstatic.net/aFSxC.jpg


Solution

  • 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: