Swift:如何阻止UICollectionView滚动时图片切换?
解决UICollectionView加载网络图片的常见问题
你的代码能实现基础的图片加载,但在cell复用场景下很容易出现图片错位、重复加载这类头疼的问题,这里给你梳理几个优化方向和改进后的代码:
1. 先还原你的原始代码
var urlArray = ["url1", "url2", ..., "urln"] func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as? MyCell cell?.objectImageView.downloadedFrom(link: urlArray[indexPath.row], contentMode: .scaleAspectFit) return cell ?? UICollectionViewCell() }
2. 核心问题拆解
- 当cell被复用时,之前未完成的下载任务会继续在新cell上加载旧图片,直接导致图片错位
- 没有缓存机制,每次滚动都重复下载相同图片,既浪费流量又拖慢性能
- 缺少下载失败的兜底处理,用户可能看到空白或者异常显示
3. 手动优化方案:任务管理+本地缓存
首先给你的MyCell加个属性,用来追踪当前的下载任务,复用前可以取消旧任务:
class MyCell: UICollectionViewCell { @IBOutlet weak var objectImageView: UIImageView! // 保存当前下载任务,复用时取消 var currentDownloadTask: URLSessionDataTask? override func prepareForReuse() { super.prepareForReuse() // 清空旧任务和旧图片,也可以换成自定义占位图 currentDownloadTask?.cancel() currentDownloadTask = nil objectImageView.image = UIImage(named: "default_placeholder") } }
然后优化downloadedFrom扩展,加入缓存和任务绑定逻辑:
extension UIImageView { func downloadedFrom(link: String, contentMode: UIView.ContentMode = .scaleAspectFit) { guard let url = URL(string: link) else { return } self.contentMode = contentMode // 先查内存缓存,有就直接用 if let cachedData = URLCache.shared.cachedResponse(for: URLRequest(url: url))?.data, let cachedImage = UIImage(data: cachedData) { self.image = cachedImage return } // 新建下载任务 let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in guard let self = self, let data = data, error == nil, let image = UIImage(data: data) else { // 下载失败时设置占位图 DispatchQueue.main.async { self.image = UIImage(named: "error_placeholder") } return } // 缓存图片到系统URLCache if let response = response { let cachedResponse = CachedURLResponse(response: response, data: data) URLCache.shared.storeCachedResponse(cachedResponse, for: URLRequest(url: url)) } // 主线程更新UI DispatchQueue.main.async { self.image = image } } task.resume() // 将任务绑定到cell,方便复用时取消 if let cell = self.superview as? MyCell { cell.currentDownloadTask = task } } }
4. 更省心的方案:用成熟的图片加载库
如果不想自己折腾缓存、任务管理这些细节,直接用第三方库就好,比如Kingfisher或者SDWebImage,它们已经把所有复杂场景都处理好了:
拿Kingfisher举例子,代码会极简:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! MyCell // 一行代码搞定缓存、复用、占位图、过渡动画 cell.objectImageView.kf.setImage( with: URL(string: urlArray[indexPath.row]), placeholder: UIImage(named: "default_placeholder"), options: [.transition(.fade(0.2))] ) return cell }
内容的提问来源于stack exchange,提问作者Duc Phan
相关产品推荐
相关产品推荐

