Загрузка и кеширование изображений в 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
, мы гарантируем, что изображение, изображение которого мы меняем, является той же самой строкой, для которой должно быть изображение. Надеюсь, это поможет!