快速切换UICollectionView分类时Cell图片错乱问题求助
解决快速切换分类时UICollectionViewCell显示随机图片的问题
我完全懂你遇到的糟心情况——快速切分类时,Cell里的图片乱跳,慢切就正常,异步生成缩略图又必须用不然卡界面,这本质是Cell复用机制和未被管控的异步任务之间的冲突:当你快速切换分类,旧Cell被复用给新数据,但之前的缩略图生成任务还在后台跑,等任务完成时,它会把图片塞到已经绑定新数据的Cell上,自然就乱了。
下面给你几个针对性的解决方案,从简单到进阶,你可以按需组合:
方案1:给Cell加标识,验证后再更新图片
这个方法的核心是让异步任务完成时,先确认当前Cell是否还对应触发任务的那组数据,只有匹配才更新图片。
修改你的PopularPostsCell,添加一个属性记录当前绑定的媒体标识:
class PopularPostsCell: UICollectionViewCell { @IBOutlet weak var myImage : UIImageView! // 记录当前Cell绑定的媒体URL,用于后续验证 private var currentMediaURL: String? func setupItems(childPostsItem: ChildPostsModel, index: Int) { // 先更新当前的媒体标识 currentMediaURL = childPostsItem.media if childPostsItem.media_type != "video" { myImage.image = nil myImage.sd_setImage(with: URL(string: childPostsItem.media), placeholderImage: UIImage(named: "")) } else { myImage.image = nil // 先清空旧图 DispatchQueue.global(qos: .background).async { let locImage = Helpers.videoSnapshot(filePathLocal: childPostsItem.media) DispatchQueue.main.async { // 关键:检查当前Cell的媒体标识是否和任务对应的数据一致 guard self.currentMediaURL == childPostsItem.media else { return // 已经被复用,放弃设置 } self.myImage.image = locImage } } } } override func prepareForReuse() { super.prepareForReuse() myImage.image = nil // 清空标识,避免旧数据干扰新绑定的内容 currentMediaURL = nil } }
方案2:取消未完成的缩略图生成任务
AVAssetImageGenerator支持取消任务,我们可以在Cell复用的时候,直接终止当前正在跑的生成任务,从根源上避免错误的图片设置。
第一步:修改Cell代码,添加生成器引用
class PopularPostsCell: UICollectionViewCell { @IBOutlet weak var myImage : UIImageView! private var currentMediaURL: String? // 保存当前的图片生成器,用于复用前取消任务 private var imageGenerator: AVAssetImageGenerator? func setupItems(childPostsItem: ChildPostsModel, index: Int) { currentMediaURL = childPostsItem.media if childPostsItem.media_type != "video" { myImage.image = nil myImage.sd_setImage(with: URL(string: childPostsItem.media), placeholderImage: UIImage(named: "")) } else { myImage.image = nil // 先取消之前可能存在的未完成任务 imageGenerator?.cancelAllCGImageGeneration() DispatchQueue.global(qos: .background).async { let locImage = Helpers.videoSnapshot(filePathLocal: childPostsItem.media, imageGenerator: &self.imageGenerator) DispatchQueue.main.async { guard self.currentMediaURL == childPostsItem.media else { return } self.myImage.image = locImage } } } } override func prepareForReuse() { super.prepareForReuse() myImage.image = nil currentMediaURL = nil // 复用前彻底终止任务并清空引用 imageGenerator?.cancelAllCGImageGeneration() imageGenerator = nil } }
第二步:修改Helpers的生成方法,传入生成器引用
static func videoSnapshot(filePathLocal: String, imageGenerator: inout AVAssetImageGenerator?) -> UIImage? { do { let asset = AVURLAsset(url: URL(string: filePathLocal)!, options: nil) let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true // 把生成器赋值给Cell的属性,方便后续取消 imageGenerator = generator let cgImage = try generator.copyCGImage(at: CMTimeMake(0, 1), actualTime: nil) let thumbnail = UIImage(cgImage: cgImage) return thumbnail } catch let error { print("*** Error generating thumbnail: \(error.localizedDescription)") return nil } }
方案3:缓存生成的视频缩略图(进阶优化)
为了避免重复生成同一视频的缩略图,你可以给缩略图加缓存,减少异步任务的数量,同时提升加载速度。比如用NSCache实现:
// 在Helpers里添加静态缓存 static let thumbnailCache = NSCache<NSString, UIImage>() static func videoSnapshot(filePathLocal: String) -> UIImage? { // 先检查缓存,有就直接返回 let cacheKey = filePathLocal as NSString if let cachedImage = thumbnailCache.object(forKey: cacheKey) { return cachedImage } do { let asset = AVURLAsset(url: URL(string: filePathLocal)!, options: nil) let imgGenerator = AVAssetImageGenerator(asset: asset) imgGenerator.appliesPreferredTrackTransform = true let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(0, 1), actualTime: nil) let thumbnail = UIImage(cgImage: cgImage) // 存入缓存 thumbnailCache.setObject(thumbnail, forKey: cacheKey) return thumbnail } catch let error { print("*** Error generating thumbnail: \(error.localizedDescription)") return nil } }
你可以把方案1和方案3组合使用,既解决了复用混乱的问题,又能提升整体加载性能,完美适配快速切换分类的场景。
内容的提问来源于stack exchange,提问作者Kerim Khasbulatov
相关产品推荐
相关产品推荐

