You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决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:下载中单元格点击无响应且动画异常

问题根源

  1. 单元格复用打断触摸状态:进度刷新时调用reloadItems会触发单元格复用,打断正在进行的触摸操作,导致touchesCancelled被触发,而你在该方法中手动切换isSelected会造成状态混乱。
  2. 手动修改isSelected冲突:你在单元格中手动切换isSelected,会干扰UICollectionView的内置选中管理逻辑,导致系统无法触发didSelectItemAt回调。
  3. 动画状态未重置:单元格复用或刷新时,未重置缩放动画的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:19:57