Быстрая прокрутка UICollectionView отображает неправильные изображения в ячейке при асинхронном добавлении новых элементов
у меня есть UICollectionView, который отображает изображения в сетке, но при быстрой прокрутке он отображает неправильное изображение в ячейке на мгновение, пока изображение не будет загружено из хранилища S3, а затем отображается правильное изображение.
Я видел вопросы и ответы, связанные с этой проблемой так и раньше, но ни одно из решений не работает для меня. Словарь let items = [[String: Any]]()
заполняется после вызова API. Мне нужна ячейка, чтобы удалить изображение из переработанной ячейки. Прямо сейчас возникает неприятный образ" танцующего " эффекта.
вот мой код:
var searchResults = [[String: Any]]()
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
let item = searchResults[indexPath.item]
cell.backgroundColor = .white
cell.itemLabel.text = item["title"] as? String
let imageUrl = item["img_url"] as! String
let url = URL(string: imageUrl)
let request = Request(url: url!)
cell.itemImageView.image = nil
Nuke.loadImage(with: request, into: cell.itemImageView)
return cell
}
class MyCollectionViewCell: UICollectionViewCell {
var itemImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.layer.cornerRadius = 0
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = .white
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
---------
}
override func prepareForReuse() {
super.prepareForReuse()
itemImageView.image = nil
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Я изменил загрузку изображения ячейки на следующий код:
cell.itemImageView.image = nil
APIManager.sharedInstance.myImageQuery(url: imageUrl) { (image) in
guard let cell = collectionView.cellForItem(at: indexPath) as? MyCollectionViewCell
else { return }
cell.itemImageView.image = image
cell.activityIndicator.stopAnimating()
}
вот мой менеджер API.
struct APIManager {
static let sharedInstance = APIManager()
func myImageQuery(url: String, completionHandler: @escaping (UIImage?) -> ()) {
if let url = URL(string: url) {
Manager.shared.loadImage(with: url, token: nil) { // internal to Nuke
guard let image = .value as UIImage? else {
return completionHandler(nil)
}
completionHandler(image)
}
}
}
если пользователь прокручивает ограничение содержимого, мой вид коллекции загрузит больше элементов. Это, по-видимому, корень проблемы, когда повторное использование ячеек повторно использует изображения. Другие поля в ячейке, такие как заголовок элемента, также меняются местами по мере появления новых элементов нагруженный.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y + view.frame.size.height) > (scrollView.contentSize.height * 0.8) {
loadItems()
}
}
вот моя функция загрузки данных
func loadItems() {
if loadingMoreItems {
return
}
if noMoreData {
return
}
if(!Utility.isConnectedToNetwork()){
return
}
loadingMoreItems = true
currentPage = currentPage + 1
LoadingOverlay.shared.showOverlay(view: self.view)
APIManager.sharedInstance.getItems(itemURL, page: currentPage) { (result, error) -> () in
if error != nil {
}
else {
self.parseData(jsonData: result!)
}
self.loadingMoreItems = false
LoadingOverlay.shared.hideOverlayView()
}
}
func parseData(jsonData: [String: Any]) {
guard let items = jsonData["items"] as? [[String: Any]] else {
return
}
if items.count == 0 {
noMoreData = true
return
}
for item in items {
searchResults.append(item)
}
for index in 0..<self.searchResults.count {
let url = URL(string: self.searchResults[index]["img_url"] as! String)
let request = Request(url: url!)
self.preHeatedImages.append(request)
}
self.preheater.startPreheating(with: self.preHeatedImages)
DispatchQueue.main.async {
// appendCollectionView(numberOfItems: items.count)
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
3 ответов
когда представление коллекции прокручивает ячейку за пределы, представление коллекции может повторно использовать ячейку для отображения другого элемента. Вот почему вы получаете ячейки из метода с именем dequeueReusableCell(withReuseIdentifier:for:)
.
вы должны убедиться, когда изображение готово, что представление изображения по-прежнему должно отображать изображение этого элемента.
я рекомендую вам изменить свой Nuke.loadImage(with:into:)
метод для закрытия вместо представления изображения:
struct Nuke {
static func loadImage(with request: URLRequest, body: @escaping (UIImage) -> ()) {
// ...
}
}
таким образом, в collectionView(_:cellForItemAt:)
, вы можете загрузить образ такой:
Nuke.loadImage(with: request) { [weak collectionView] (image) in
guard let cell = collectionView?.cellForItem(at: indexPath) as? MyCollectionViewCell
else { return }
cell.itemImageView.image = image
}
если представление коллекции больше не отображает элемент в какой-либо ячейке, изображение будет отброшено. Если представление коллекции отображает элемент в любой ячейке (даже в другой ячейке), вы сохраните изображение в правильном представлении изображения.
попробуйте это:
всякий раз, когда клетка используется повторно, ее prepareForReuse
метод называется. Вы можете перезагрузить свой телефон здесь. В вашем случае вы можете установить изображение по умолчанию в виде изображения здесь, пока исходное изображение не будет загружено.
override func prepareForReuse()
{
super.prepareForReuse()
self.imageView.image = UIImage(named: "DefaultImage"
}
со страницы проекта Nuke:
Nuke.метод loadImage (with:into:) отменяет предыдущий невыполненный запрос, связанный с целью. Nuke содержит слабую ссылку на цель, когда цель освобождается, связанный запрос автоматически отменяется.
и, насколько я могу судить, Вы правильно используете их пример кода:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
...
cell.imageView.image = nil
Nuke.loadImage(with: url, into: cell.imageView)
...
}
вы явно устанавливаете изображение UIImageViews на ноль, поэтому на самом деле должно быть он не может отображать изображение, пока Nuke не загрузит новое изображение в цель.
также Nuke отменит предыдущий запрос. Я сам использую Nuke и не имею этих проблем. Вы уверены, что ваш "измененный код" не является неисправной частью здесь? Для меня все кажется правильным, и я не вижу проблемы, почему это не должно работать.
одной из возможных проблем может быть перезагрузка всего представления коллекции. Мы всегда должны стараться обеспечить лучший пользовательский опыт, поэтому, если вы измените источник данных представления коллекции, попробуйте использовать performBatchUpdates для анимации вставок/обновлений/перемещений / удалений.
хорошая библиотека, которая автоматически может позаботиться об этом, например, Dwifft.