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

UIProgressBar从后台返回后无视代码停留在随机值问题

问题解决:UITableViewCell中ProgressBar后台/模态返回后UI不更新

问题根源

  1. RunLoop模式限制:你使用的Timer.scheduledTimer默认绑定到Main RunLoop的default模式,当模态视图弹出时,Main RunLoop会切换到UITrackingRunLoopMode,此时timer会暂停;应用进入后台时,系统也会暂停非必要timer。返回前台后timer恢复,但cell的UI可能因视图层级变化未正确刷新。
  2. Cell复用风险:直接在willDisplay中给cell绑定timer,加上强制类型转换,容易导致cell复用后多个timer同时操作同一cell,造成状态混乱。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 22:47:14