Я загружаю изображения с сервера и показываю их в collectionView. Я кеширую изображения, чтобы пользователь получил быстрый ответ сервера и не было сбоев в пользовательском интерфейсе. Пока изображение не загружено, я также добавил изображение-заполнитель.
Но в моем выводе изображение реплицируется в других ячейках, и изображения не кэшируются NSCache должным образом..
Вот код ниже
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
}
}
Выходной GIF
Из-за ограничения размера стека изображение выше имеет низкое качество. Если вам нужно проверить gif в полном размере, см.: https://imgur.com/tcjMWgc
@CerlinBoss В какой строке вы говорите?
Я добавил это как ответ
вы можете использовать фреймворк SDWebImage, он все сделает за вас
@AlexandrKolesnik Вот что я пытаюсь научиться работать с NSCache. Есть ли у вас какое-либо решение по этой проблеме, поскольку я уже пробовал миллионы решений и комбинаций, которые нашел в Google.
все ячейки можно использовать повторно, поэтому вы должны установить изображение по умолчанию в cellForRow перед установкой изображения из кеша, чем вы должны связать свою ячейку.
@AlexandrKolesnik Можешь перепроверить обновленный downloadImage func. Я делаю то же самое, что вы сказали. Если чего-то все еще не хватает, дайте мне знать, я буду рад попробовать ваш ответ.





Это потому, что ячейка многоразовая.
Верхняя ячейка используется повторно, но ячейка не обновляет изображение, поскольку изображение ячейки уже установлено.
Вы должны расширить UIImage для обновления изображения ячейки
как это:
расширение UIImageView {
func loadImageNone(_ urlString: String) {
if let cacheImage = imageCache.object(forKey: urlString as NSString) {
self.run(with: cacheImage)
return
} else {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completion(nil)
return
}
if let respo = response as? HTTPURLResponse {
if let imageData = data, let image = UIImage(data: imageData) {
imageCache.setObject(image, forKey: urlString as NSString)
DispatchQueue.main.async {
self.image = image
}
}
}
}.resume()
}
func run(with image: UIImage) {
UIView.transition(with: self,
duration: 0.5,
options: [],
animations: { self.image = image },
completion: nil)
}
}
Причина, по которой вы упомянули, верна, но простая анимация смены изображения не решает проблему. Также оператор if не требует возврата, поскольку после оператора else нет кода.
Общая разница ч/б этого ответа и моего заключается в том, что я делаю не так, как в расширении. Остальная часть кода такая же. P.S. Я также попробовал ответ Shobhakar Tiwari от здесь, который точно такой же, как ваш ответ.
Измените свой метод, как показано ниже
// This method is getting called for all the cells
func downloadImage(url: URL, imageView: UIImageView) {
// 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 = UIImage(named: "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 = UIImage(named: "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
imageView.image = image
} else {
// You should be giving an option to retry the image here
imageView.image = UIImage(named: "placeholder")
}
}
}.resume()
}
}
И назовите это внутри cellForItemAt лайком
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DummyCollectionViewCell", for: indexPath) as! DummyCollectionViewCell
let str = arrURLs[indexPath.item]
if let url = URL(string: str) {
downloadImage(url: url, imageView: cell.imgView)
} else {
cell.imgView.image = UIImage(named: "placeholder")
}
return cell
}
Я изменил свой код на ваш и все равно получаю тот же ответ. Многие ответы и блоги имеют тот же ответ, что и мы, но не знаем, откуда взялся этот сбой.
@dahiya_boy Код, который вы разместили в вопросе, имеет проблему. Оператор else будет ожидать завершения URLSession для установки изображения (пока это не произойдет, будет отображаться старое изображение из повторно используемой ячейки.)
о, да, это один из пунктов. Согласен, но после добавления вашего кода все равно есть глюки. Как только все изображения загружаются в кеш, все работает правильно. Таким образом, может возникнуть проблема со временем получения изображения с сервера. Кстати, если я перезапущу приложение, то я получу значение из кеша или нет, или мне нужно явно хранить кеш, чтобы сохранить изображения в кеше?
Вы должны вызвать prepareForReuse() с super для пользовательского класса UICollectionViewCell. Это гарантирует, что удаление из очереди вызывается для каждой строки и получает кеш.
override func prepareForReuse() {
super.prepareForReuse()
reuseAction()
}
Из документа Apple Также, когда изображение загружается, вы должны:
self.collectionView.reloadData()
или перезагрузить строку, если вы держите ссылку на строку, когда изображение заканчивает загрузку
let indexSet = IndexSet(integer: indexPath.section)
collectionView.reloadSections(indexSet)
Не могли бы вы объяснить, какое отношение ваш ответ имеет к моему вопросу?
Я думаю, проблема в вашем обработчике ответов, вы устанавливаете кеш для запрашиваемого URL-адреса, а не для URL-адреса из ответа, я немного изменил ваш код, попробуйте, надеюсь, это поможет вам
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()
}
}
И
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)
}
}
}
ОБНОВЛЕНО:
Я пытался, но изображения реплицируются в другой ячейке. ?
ты заменил свой код на мой? попробуйте переустановить приложение, чтобы очистить кеш перед использованием
Можете ли вы объяснить, что было не так? И я также хотел знать, что мы используем кеш для хранения временных значений. Поэтому каждый раз, когда я запускаю изображение, его кеш очищается, а изображения загружаются повторно. Но в SDWebImage, как только изображение добавляется в кеш, оно остается в кеше до тех пор, пока мы явно не очистим его. Почему?
Как я уже сказал выше, ячейка может использоваться повторно, как и все ее содержимое. При отладке я заметил, что imageView с одним и тем же адресом загружает разные URL-адреса, поэтому я добавил переменную времени выполнения для обновления URL-адреса кеша в соответствии со строкой ячейки. Вы можете посмотреть код SDWebImage, я думаю, что они архивируют кеш
USE THIS IMAGE LOADER EXTENSION
let imageCache = NSCache<AnyObject, AnyObject>()
class ImageLoader: UIImageView {
var imageURL: URL?
let activityIndicator = UIActivityIndicatorView()
func loadImageWithUrl(_ url: URL) {
// setup activityIndicator...
activityIndicator.color = .darkGray
addSubview(activityIndicator)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
imageURL = url
image = nil
activityIndicator.startAnimating()
// retrieves image if already available in cache
if let imageFromCache = imageCache.object(forKey: url as AnyObject) as? UIImage {
self.image = imageFromCache
activityIndicator.stopAnimating()
return
}
// image does not available in cache.. so retrieving it from url...
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil {
print(error as Any)
self.activityIndicator.stopAnimating()
return
}
DispatchQueue.main.async(execute: {
if let unwrappedData = data, let imageToCache = UIImage(data: unwrappedData) {
if self.imageURL == url {
self.image = imageToCache
}
imageCache.setObject(imageToCache, forKey: url as AnyObject)
}
self.activityIndicator.stopAnimating()
})
}).resume()
}
}
** design controller **
import UIKit
class ImageController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let cellId = "cellId"
lazy var imagesSliderCV: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.showsHorizontalScrollIndicator = false
cv.delegate = self
cv.dataSource = self
cv.isPagingEnabled = true
cv.register(ImageSliderCell.self, forCellWithReuseIdentifier: self.cellId)
return cv
}()
//
// Mark:- CollectionView Methods........
//
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 collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrURLs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ImageSliderCell
let ImagePath = arrURLs[indexPath.item]
if let strUrl = ImagePath.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
let imgUrl = URL(string: strUrl) {
cell.frontImg.loadImageWithUrl(imgUrl)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: screenWidth, height: 288)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func setupAutoLayout(){
NSLayoutConstraint.activate([
imagesSliderCV.leftAnchor.constraint(equalTo: view.leftAnchor),
imagesSliderCV.rightAnchor.constraint(equalTo: view.rightAnchor),
imagesSliderCV.topAnchor.constraint(equalTo: view.topAnchor),
imagesSliderCV.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}
**collectionView cell **
import UIKit
class ImageSliderCell: UICollectionViewCell {
//
let frontImg: ImageLoader = {
let img = ImageLoader()
img.translatesAutoresizingMaskIntoConstraints = false
img.contentMode = .scaleAspectFill
img.clipsToBounds = true
return img
}()
//
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(frontImg)
setupAutolayout()
}
func setupAutolayout(){
frontImg.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
frontImg.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
frontImg.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
frontImg.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ВЫХОД: -
Установите местозаполнитель
UIImageViewв операторе else методаdownloadImage