Download and cache images in UITableViewCell

JEL picture JEL · Jun 25, 2017 · Viewed 8.6k times · Source

Note: Please no libraries. This is important for me to learn. Also, there are a variety of answers on this but none that I found solves the issue nicely. Please don't mark as duplicate. Thanks in advance!

The problem I have is that if you scroll really fast in the table, you will see old images and flickering.

  • The solution from the questions I read is to cancel the URLSession data request. But I do not know how to do that at the correct place and time. There might be other solutions but not sure.

This is what I have so far:

Image cache class

class Cache {

    static let shared = Cache()

    private let cache = NSCache<NSString, UIImage>()
    var task = URLSessionDataTask()
    var session = URLSession.shared

    func imageFor(url: URL, completionHandler: @escaping (image: Image? error: Error?) -> Void) {
            if let imageInCache = self.cache.object(forKey: url.absoluteString as NSString)  {
                completionHandler(image: imageInCache, error: nil)
                return
            }

            self.task = self.session.dataTask(with: url) { data, response, error in

                if let error = error {
                    completionHandler(image: nil, error: Error)
                    return
                }

                let image = UIImage(data: data!)

                    self.cache.setObject(image, forKey: url.absoluteString as NSString)
                    completionHandler(image: image, error: nil)
                }

            self.task.resume()
    }
}

Usage

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    let myImage = images[indexPath.row]

    if let imageURL = URL(string: myImage.urlString) {
        photoImageView.setImage(from: imageURL)
    }
    return cell
}

Any thoughts?

Answer

Mamta picture Mamta · Jun 25, 2017

Swift 3:

Flickering can be avoided by this way:

Use the following code in public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

cell.photoImageView.image = nil //or keep any placeholder here
cell.tag = indexPath.row
let task = URLSession.shared.dataTask(with: imageURL!) { data, response, error in
    guard let data = data, error == nil else { return }

    DispatchQueue.main.async() {
        if cell.tag == indexPath.row{
            cell.photoImageView.image = UIImage(data: data) 
        }
    }
}
task.resume()

By checking cell.tag == indexPath.row, we are assuring that the imageview whose image we are changing, is the same row for which the image is meant to be. Hope it helps!