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

iOS倒计时计时器后台切换异常:首次启动跳负及Label更新失败

解决后台返回后倒计时更新的异常问题

看起来你遇到的两个问题,核心原因是重复注册通知观察者和未正确管理Timer状态,还有首次启动时的逻辑判断缺失。我们一步步来修复:

问题1:首次打开倒计时立即变负数

这个问题的根源是你在viewWillAppear中重复添加通知观察者——每次页面出现都会注册一次didEnterBackground和didBecomeActive通知,导致同一个事件会触发多次对应的方法。比如多次调用startApp1,创建多个Timer同时执行updateCountdown,每秒多次减少timeDuration,自然会出现负数。

修复步骤:

  1. 把通知观察者的注册移到viewDidLoad中,因为这个方法只会执行一次:
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(pauseApp1), name: UIApplication.didEnterBackgroundNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(startApp1), name: UIApplication.didBecomeActiveNotification, object: nil)
}
  1. 记得在deinit中移除观察者,避免内存泄漏:
deinit {
    NotificationCenter.default.removeObserver(self)
}

问题2:无法按预期更新timeDurationLabel + 首次启动倒计时异常

另一个问题是,首次启动时didBecomeActive通知会触发startApp1,但此时你还没点击开始按钮,timeDuration是初始值0,执行updateCountdown后直接变成-1。同时,后台返回时的时间计算虽然逻辑方向对,但缺少对倒计时是否已启动的判断,而且Timer的创建没有先销毁旧的,导致多个Timer冲突。

修复步骤:

  1. 添加一个状态变量标记倒计时是否正在运行:
var isCounting = false
  1. 在startButtonTapped中标记状态为true,并创建Timer前先销毁旧的:
@IBAction func startButtonTapped(_ sender: UIButton) {
    // 先销毁旧Timer,避免重复运行
    timer.invalidate()
    self.isCounting = true
    self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
    self.timeDuration = Int(self.countdownInterval) - (self.remainder % 60)
    self.startButton.initActionBtn(colorType: .gray, title: "START")
    self.coundownPickerButton.isEnabled = false
    self.circularProgress.animate(fromAngle: 0, toAngle: 360, duration: TimeInterval(timeDuration)) { completed in
        if completed {
            print("animation stopped, completed")
        } else {
            print("animation stopped, was interrupted")
        }
    }
}
  1. 修改pauseApp1和startApp1,只有当倒计时正在运行时才处理:
@objc func pauseApp1() {
    guard isCounting else { return }
    timer.invalidate()
    currentBackgroundDate = Date()
}

@objc func startApp1() {
    guard isCounting else { return }
    // 计算后台停留的时间差,直接得到正数秒数
    let timeElapsed = Date().timeIntervalSince(currentBackgroundDate)
    timeDuration -= Int(timeElapsed)
    // 防止倒计时变成负数
    if timeDuration < 0 {
        timeDuration = 0
    }
    // 立即更新Label显示最新剩余时间
    timeDurationLabel.text = timeFormatted(timeDuration)
    // 先销毁旧Timer再创建新的
    timer.invalidate()
    self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
}
  1. 在updateCountdown中,当倒计时结束时重置状态并修正Label显示:
@objc func updateCountdown() {
    self.timeDuration -= 1
    self.timeDurationLabel.text = timeFormatted(timeDuration)
    if timeDuration <= 0 {
        timer.invalidate()
        self.isCounting = false
        self.audioPlayer.play()
        self.startButton.initActionBtn(colorType: .green, title: "START")
        // 确保Label显示0而不是负数
        timeDurationLabel.text = timeFormatted(0)
    }
}

额外优化点

  • 计算时间差时用Date().timeIntervalSince(currentBackgroundDate)更直观,得到的是后台停留的正数秒数,直接减去这个值即可,不用处理负数逻辑。
  • 每次创建Timer前都调用timer.invalidate(),确保同一时间只有一个Timer在运行,避免多Timer冲突导致的倒计时跳变。
  • 当timeDuration小于0时强制设为0,防止Label显示负数。

这样修改后,应该就能解决你遇到的两个问题:首次打开不会触发无效的倒计时,后台返回后能正确更新剩余时间,并且Label也能正常显示了。

内容的提问来源于stack exchange,提问作者RushML

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:22:56