iOS倒计时计时器后台切换异常:首次启动跳负及Label更新失败
解决后台返回后倒计时更新的异常问题
看起来你遇到的两个问题,核心原因是重复注册通知观察者和未正确管理Timer状态,还有首次启动时的逻辑判断缺失。我们一步步来修复:
问题1:首次打开倒计时立即变负数
这个问题的根源是你在viewWillAppear中重复添加通知观察者——每次页面出现都会注册一次didEnterBackground和didBecomeActive通知,导致同一个事件会触发多次对应的方法。比如多次调用startApp1,创建多个Timer同时执行updateCountdown,每秒多次减少timeDuration,自然会出现负数。
修复步骤:
- 把通知观察者的注册移到
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) }
- 记得在
deinit中移除观察者,避免内存泄漏:
deinit { NotificationCenter.default.removeObserver(self) }
问题2:无法按预期更新timeDurationLabel + 首次启动倒计时异常
另一个问题是,首次启动时didBecomeActive通知会触发startApp1,但此时你还没点击开始按钮,timeDuration是初始值0,执行updateCountdown后直接变成-1。同时,后台返回时的时间计算虽然逻辑方向对,但缺少对倒计时是否已启动的判断,而且Timer的创建没有先销毁旧的,导致多个Timer冲突。
修复步骤:
- 添加一个状态变量标记倒计时是否正在运行:
var isCounting = false
- 在
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") } } }
- 修改
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) }
- 在
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
相关产品推荐
相关产品推荐

