如何避免UITableViewCell滚动时更新内容(含下载进度显示场景)
我之前处理过不少UITableView里动态更新进度条的场景,用标签绑定的方式其实很容易踩坑——比如单元格复用的时候,标签会和下载项错位,导致进度条更新到错误的单元格上。给你几个更可靠的方案,都是实际项目里验证过的:
方案1:模型绑定(最推荐,从根源避免复用问题)
核心思路是把下载状态和进度直接存在对应的数据模型里,单元格根据模型状态自动更新:
- 先给你的下载数据模型(比如叫
DownloadItem)加两个属性:struct DownloadItem { let id: String var isDownloading: Bool = false var progress: Float = 0.0 // 其他原有属性... } - 自定义
MyCell时,添加一个配置方法,让单元格接收模型并更新UI:class MyCell: UITableViewCell { @IBOutlet weak var progressView: UIProgressView! @IBOutlet weak var downloadButton: UIButton! func configure(with item: DownloadItem) { // 根据模型状态控制进度条显示/隐藏 progressView.isHidden = !item.isDownloading progressView.progress = item.progress // 按钮文字也可以跟着切换,比如下载中显示"暂停" downloadButton.setTitle(item.isDownloading ? "暂停" : "下载", for: .normal) } } - 在
tableView(_:cellForRowAt:)里给单元格传模型:func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell let item = downloadItems[indexPath.row] cell.configure(with: item) // 给下载按钮绑定点击事件,用indexPath.row关联对应的模型 cell.downloadButton.tag = indexPath.row cell.downloadButton.addTarget(self, action: #selector(startDownload(_:)), for: .touchUpInside) return cell } - 下载进度更新时,直接修改对应模型的
progress,然后刷新单元格:func updateDownloadProgress(for itemID: String, progress: Float) { guard let index = downloadItems.firstIndex(where: { $0.id == itemID }) else { return } downloadItems[index].progress = progress // 只刷新对应行,避免整个表格闪烁 tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none) }
方案2:闭包回调(适合不想改动原有模型的场景)
如果暂时不想修改数据模型,可以用闭包让下载任务直接绑定到对应的单元格:
- 在
MyCell里添加一个进度更新的闭包属性:class MyCell: UITableViewCell { @IBOutlet weak var progressView: UIProgressView! // 进度更新回调 var onProgressUpdate: ((Float) -> Void)? func showProgress() { progressView.isHidden = false } } - 在单元格配置时,把闭包和下载任务关联:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell let item = downloadItems[indexPath.row] cell.progressView.isHidden = true // 绑定闭包,让下载任务能直接更新这个单元格的进度 cell.onProgressUpdate = { [weak cell] progress in DispatchQueue.main.async { cell?.progressView.progress = progress } } cell.downloadButton.tag = indexPath.row cell.downloadButton.addTarget(self, action: #selector(startDownload(_:)), for: .touchUpInside) return cell } - 启动下载任务时,拿到对应单元格的闭包来更新进度:
@objc func startDownload(_ sender: UIButton) { let index = sender.tag let item = downloadItems[index] guard let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? MyCell else { return } cell.showProgress() // 假设这是你的下载任务,进度回调里调用单元格的闭包 let downloadTask = createDownloadTask(for: item) { progress in cell.onProgressUpdate?(progress) } // 保存任务,方便后续暂停/取消 activeDownloads[item.id] = downloadTask }
为什么不推荐用标签绑定?
UITableView的单元格是复用机制,当你滚动表格时,旧单元格会被重新分配给新的行,这时候标签对应的下载项就会混乱,导致进度条更新到完全不相关的单元格上,排查起来特别麻烦。
内容的提问来源于stack exchange,提问作者beev
相关产品推荐
相关产品推荐

