解决UICollectionView更新卡顿及下载中单元格点击异常问题
问题1:UICollectionView下载进度更新导致滚动卡顿
问题根源
你的reloadItem方法存在核心性能问题:每次更新都重建完整的snapshot并全量应用,相当于刷新整个集合视图。而下载进度回调频率极高(每写入少量数据就触发一次),频繁的全量刷新会阻塞主线程,直接引发滚动卡顿。
另外下载管理器的进度计算逻辑冗余:calculateProgress方法通过遍历所有下载任务计算进度,但当前回调已经拿到了对应的downloadTask,完全没必要全局遍历。
修复方案
1. 优化单个Item刷新逻辑
基于现有snapshot修改,避免全量重建:
func reloadItem(indexPath: IndexPath) { guard let needReloadItem = dataSource?.itemIdentifier(for: indexPath) else { return } // 复用当前已有的snapshot,无需重新构建所有sections和items var snapshot = dataSource!.snapshot() snapshot.reloadItems([needReloadItem]) // 关闭动画,避免频繁刷新的视觉抖动 dataSource?.apply(snapshot, animatingDifferences: false) }
2. 简化下载进度计算
直接用回调传入的downloadTask计算进度,删除冗余的calculateProgress方法:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { guard totalBytesExpectedToWrite > 0, let onProgress = onProgress else { return } // 直接基于当前任务计算进度 let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite) onProgress(session.getSessionDescription(), Int(session.accessibilityHint!)!, progress) }
3. 限制进度刷新频率
添加节流逻辑,避免过于频繁的UI更新:
// 在ViewController中添加节流标记 private var lastProgressUpdateTime: TimeInterval = 0 // 修改进度回调逻辑 downloadManager.onProgress = { [weak self] (row, collection, progress) in guard let self = self else { return } let currentTime = Date().timeIntervalSince1970 // 每100ms仅更新一次UI guard currentTime - self.lastProgressUpdateTime > 0.1 else { return } self.lastProgressUpdateTime = currentTime DispatchQueue.main.async { self.items[row - 1].progress = progress self.items[row - 1].state = progress == 1.0 ? .completed : .downloading self.reloadItem(indexPath: .init(row: row - 1, section: 0)) } }
问题2:下载中单元格点击无响应且动画异常
问题根源
- 单元格复用打断触摸状态:进度刷新时调用
reloadItems会触发单元格复用,打断正在进行的触摸操作,导致touchesCancelled被触发,而你在该方法中手动切换isSelected会造成状态混乱。 - 手动修改
isSelected冲突:你在单元格中手动切换isSelected,会干扰UICollectionView的内置选中管理逻辑,导致系统无法触发didSelectItemAt回调。 - 动画状态未重置:单元格复用或刷新时,未重置缩放动画的transform状态,导致动画停留在缩小状态。
修复方案
1. 移除手动选中状态控制,交给系统管理
删除touchesEnded和touchesCancelled中的isSelected.toggle(),并添加动画交互权限、复用重置逻辑:
class DCell: UICollectionViewCell, SelfConfiguringCell { private func animateScale(to scale: CGFloat, duration: TimeInterval) { // 添加.allowUserInteraction选项,确保动画过程中可交互 UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: [.allowUserInteraction], animations: { self.stackView.transform = .init(scaleX: scale, y: scale) self.textView.transform = .init(scaleX: scale, y: scale) }, completion: nil) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) animateScale(to: 0.9, duration: 0.4) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) animateScale(to: 1, duration: 0.38) } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesCancelled(touches, with: event) animateScale(to: 1, duration: 0.38) } // 单元格复用前重置transform状态 override func prepareForReuse() { super.prepareForReuse() stackView.transform = .identity textView.transform = .identity } }
2. 可选:避免触摸期间刷新进度
添加触摸状态判断,在用户触摸时暂时跳过进度刷新,触摸结束后补更:
// 在ViewController中添加触摸状态标记 private var isUserTouching = false // 重写触摸方法记录状态 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) isUserTouching = true } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) isUserTouching = false // 触摸结束后立即刷新当前可见单元格的进度 collectionView.indexPathsForVisibleItems.forEach { reloadItem(indexPath: $0) } } // 修改进度回调逻辑 downloadManager.onProgress = { [weak self] (row, collection, progress) in guard let self = self else { return } let currentTime = Date().timeIntervalSince1970 guard currentTime - self.lastProgressUpdateTime > 0.1 else { return } self.lastProgressUpdateTime = currentTime DispatchQueue.main.async { self.items[row - 1].progress = progress self.items[row - 1].state = progress == 1.0 ? .completed : .downloading // 用户触摸时跳过刷新 guard !self.isUserTouching else { return } self.reloadItem(indexPath: .init(row: row - 1, section: 0)) } }
内容的提问来源于stack exchange,提问作者User
相关产品推荐
相关产品推荐

