我有一个UICollectionView,它在网格中显示图像但是当我快速滚动时,它会暂时在单元格中显示错误的图像,直到从S3存储器下载图像,然后显示正确的图像.
我以前在SO上看过有关这个问题的问题和答案,但没有一个解决方案适合我.字典允许在API调用之后填充items = [[String:Any]]().我需要细胞从废弃的细胞中丢弃图像.现在有一种令人不快的形象“跳舞”效果.
这是我的代码:
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 = $0.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() } }
解决方法
当集合视图滚动单元格超出边界时,集合视图可以重复使用单元格来显示不同的项目.这就是为什么你从名为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 }
如果集合视图不再显示任何单元格中的项目,则将丢弃该图像.如果集合视图在任何单元格中显示项目(即使在不同的单元格中),您将将图像存储在正确的图像视图中.