UIProgressBar从后台返回后无视代码停留在随机值问题
问题解决:UITableViewCell中ProgressBar后台/模态返回后UI不更新
问题根源
- RunLoop模式限制:你使用的
Timer.scheduledTimer默认绑定到Main RunLoop的default模式,当模态视图弹出时,Main RunLoop会切换到UITrackingRunLoopMode,此时timer会暂停;应用进入后台时,系统也会暂停非必要timer。返回前台后timer恢复,但cell的UI可能因视图层级变化未正确刷新。 - Cell复用风险:直接在
willDisplay中给cell绑定timer,加上强制类型转换,容易导致cell复用后多个timer同时操作同一cell,造成状态混乱。 - UI刷新时机问题:即使进度值计算正确,若RunLoop处于非活跃状态,
setProgress的UI更新可能被延迟或忽略。
方案一:将计时逻辑移至ViewController(推荐,更符合MVC架构)
让Cell仅负责展示,把计时和数据计算放在ViewController层,通过定时更新指定行来避免复用问题。
1. 定义数据模型
struct CountdownItem { let dateTarget: Date let dateWhenSet: Date var timer: Timer? }
2. 维护数据数组并修改代理方法
// ViewController中定义数据数组 var countdownItems: [CountdownItem] = [] // 需自行初始化数据 func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let countdownCell = cell as? CountdownTable else { return } let item = countdownItems[indexPath.row] // 绑定timer并关联indexPath,避免复用混淆 let timer = Timer(timeInterval: 1, repeats: true) { [weak self, weak countdownCell] timer in guard let self = self, let cell = countdownCell else { timer.invalidate() return } let now = Date() let nowToEvent = item.dateTarget.timeIntervalSince(now) let thenToEvent = item.dateTarget.timeIntervalSince(item.dateWhenSet) // 倒计时结束时停止timer guard thenToEvent > 0 else { timer.invalidate() cell.countdownBar.setProgress(1.0, animated: true) return } let elapsed = thenToEvent - nowToEvent let progress = Float(elapsed / thenToEvent) // 强制在主线程更新UI DispatchQueue.main.async { cell.countdownBar.setProgress(progress, animated: true) cell.countdownDate.text = item.dateTarget.timeFromDate(from: now) } } // 保存timer到数据模型,方便后续销毁 countdownItems[indexPath.row].timer = timer // 将timer加入RunLoop的common模式,确保模态视图/滚动时正常运行 RunLoop.current.add(timer, forMode: .common) } func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { // 销毁timer,释放资源 countdownItems[indexPath.row].timer?.invalidate() countdownItems[indexPath.row].timer = nil }
方案二:修复原Cell的Timer配置(快速修改)
如果不想重构,可调整Timer的RunLoop模式,并优化cell引用逻辑:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let countdownCell = cell as? CountdownTable else { return } // 先销毁旧timer,防止复用冲突 countdownCell.timer?.invalidate() let timer = Timer(timeInterval: 1, repeats: true) { [weak countdownCell] timer in guard let cell = countdownCell else { timer.invalidate() return } let now = Date() let nowToEvent = cell.dateTarget.timeIntervalSince(now) let thenToEvent = cell.dateTarget.timeIntervalSince(cell.dateWhenSet) guard thenToEvent > 0 else { timer.invalidate() cell.countdownBar.setProgress(1.0, animated: true) return } let elapsed = thenToEvent - nowToEvent let progress = Float(elapsed / thenToEvent) DispatchQueue.main.async { cell.countdownBar.setProgress(progress, animated: true) cell.countdownDate.text = cell.dateTarget.timeFromDate(from: now) } } countdownCell.timer = timer // 加入common模式,避免模态视图时timer暂停 RunLoop.current.add(timer, forMode: .common) } func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let countdownCell = cell as? CountdownTable else { return } countdownCell.timer?.invalidate() countdownCell.timer = nil }
核心修复点
- 用
weak引用cell,避免循环引用,同时在cell销毁时自动停止timer - 将timer加入RunLoop的
.common模式,确保模态视图、列表滚动时timer正常运行 - 显式在主线程更新UI,规避RunLoop状态导致的刷新延迟
- 启动新timer前销毁旧timer,防止cell复用后多个timer冲突
- 替换强制类型转换为
guard let,提升代码安全性
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

