Загрузка и кеширование изображений в UITableViewCell

Примечание. Пожалуйста, никаких библиотек. Это важно для меня, чтобы учиться. Кроме того, есть множество ответов на это, но ни один из них, который я нашел, не решает проблему. Не указывайте в качестве дубликата. Спасибо заранее!

Проблема заключается в том, что если вы быстро прокрутите в таблице, вы увидите старые изображения и мерцание.

  • Решение вопросов, которые я прочитал, это отменить URLSession запрос данных. Но я не знаю, как это сделать в правильном месте и время. Могут быть другие решения, но не уверены.

Это то, что у меня есть до сих пор:

Класс кеша изображений

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()
    }
}

Использование

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
}

Любые мысли?

Ответ 1

Несколько проблем:

  • Один из возможных источников мерцания заключается в том, что при обновлении изображения асинхронно вы действительно хотите сначала очистить изображение, так что вы не увидите изображения для предыдущей строки ячейки с повторным использованием/удалением таблицы. Перед началом асинхронного извлечения изображения убедитесь, что перед просмотром изображения image до nil установлено значение nil. Или, возможно, объедините это с логикой "placeholder", которую вы увидите во множестве категорий UIImageView для поиска изображений с синхронизацией.

    Например:

    extension UIImageView {
    
        func setImage(from url: URL, placeholder: UIImage? = nil) {
            image = placeholder               // use placeholder (or if `nil`, remove any old image, before initiating asynchronous retrieval
    
            ImageCache.shared.image(for: url) { [weak self] result in
                switch result {
                case .success(let image):
                    self?.image = image
    
                case .failure:
                    break
                }
            }
        }
    }
    
  • Другая проблема заключается в том, что если вы прокручиваете очень быстро, у повторно используемого изображения может быть еще старый запрос на получение изображений. Вы действительно должны, когда вы вызываете свой метод поиска aync-кода UIImageView, вы должны отменить и предыдущий запрос, связанный с этой ячейкой.

    Хитрость заключается в том, что если вы делаете это в расширении UIImageView, вы не можете просто создать новое сохраненное свойство, чтобы отслеживать старый запрос. Поэтому вы часто используете "связанные значения" для отслеживания предыдущих запросов.

Ответ 2

Swift 3:

Мерцание можно избежать следующим образом:

Используйте следующий код в 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()

Проверяя cell.tag == indexPath.row, мы гарантируем, что изображение, изображение которого мы меняем, является той же самой строкой, для которой должно быть изображение. Надеюсь, это поможет!