暂停后显示倒计时的标签有时会不同步。舍入误差?

Labels displaying countdown sometimes out of sync after pausing. Rounding errors?

我有一个使用 Timer 进行倒计时的应用程序。倒计时跟踪多个步骤(都以相同的间隔)以及剩余的总时间,并相应地更新 2 个单独的 UILabel。有时,标签会不同步。

我不能肯定地说,但我认为这可能仅在我有时暂停倒计时时发生,并且通常在第一步之后的步骤中发生。这在最后一步最为明显,此时两个标签应该显示完全相同的内容,但有时会相差 1 秒。

另一个棘手的事情是,有时在时间不同步后暂停和恢复会使它恢复同步。

我猜我在暂停代码中遇到了一些奇怪的事情 and/or 在步骤之间移动,或者可能是 TimeIntervals 的计算和格式化。此外,我在计算的 TimeIntervals 上使用 rounded(),因为我注意到每 1 秒更新一次计时器,标签会冻结并跳过很多秒。但我不确定这是否是解决此问题的最佳方法。

这是相关代码。 (仍然需要重构,但希望它很容易理解,我还是个初学者)

@IBAction func playPauseTapped(_ sender: Any) {
        if timerState == .running {
            //pause timer
            pauseAnimation()
            timer.invalidate()
            timerState = .paused
            pausedTime = Date()
            playPauseButton.setImage(UIImage(systemName: "play.circle"), for: .normal)
        } else if timerState == .paused {
            //resume paused timer
            guard let pause = pausedTime else { return }
            let pausedInterval = Date().timeIntervalSince(pause)
            startTime = startTime?.addingTimeInterval(pausedInterval)
            endTime = endTime?.addingTimeInterval(pausedInterval)
            currentStepEndTime = currentStepEndTime?.addingTimeInterval(pausedInterval)
            pausedTime = nil
            startTimer()
            resumeAnimation()
            timerState = .running
            playPauseButton.setImage(UIImage(systemName: "pause.circle"), for: .normal)
        } else {
            //first run of brand new timer
            startTimer()
            startProgressBar()
            startTime = Date()
            if let totalTime = totalTime {
                endTime = startTime?.addingTimeInterval(totalTime)
            }
            currentStepEndTime = Date().addingTimeInterval(recipeInterval)
            timerState = .running
            playPauseButton.setImage(UIImage(systemName: "pause.circle"), for: .normal)

            currentWater += recipeWater[recipeIndex]
            currentWeightLabel.text = "\(currentWater)g"
        }
    }

func startTimer() {
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
    }

@objc func runTimer() {
        let currentTime = Date()

        guard let totalTimeLeft = endTime?.timeIntervalSince(currentTime).rounded() else { return }

        guard let currentInterval = currentStepEndTime?.timeIntervalSince(currentTime).rounded() else { return }

        //end of current step
        if currentInterval <= 0 {
            //check if end of recipe
            if recipeIndex < recipeWater.count - 1 {
                //move to next step
                totalTimeLabel.text = totalTimeLeft.stringFromTimeInterval()
                currentStepEndTime = Date().addingTimeInterval(recipeInterval)
                startProgressBar()
                currentStepTimeLabel.text = recipeInterval.stringFromTimeInterval()
                stepsTime += recipeInterval
                recipeIndex += 1
                //update some ui
            } else {
                //last step
                currentStepTimeLabel.text = "00:00"
                totalTimeLabel.text = "00:00"
                timer.invalidate()
                //alert controller saying finished
            }
        } else {
            //update time labels
            currentStepTimeLabel.text = currentInterval.stringFromTimeInterval()
            totalTimeLabel.text = totalTimeLeft.stringFromTimeInterval()
        }

    }

extension TimeInterval {

    func stringFromTimeInterval() -> String {

        let time = NSInteger(self)

        let seconds = time % 60
        let minutes = (time / 60) % 60

        return String(format: "%0.2d:%0.2d",minutes,seconds)

    }
}

编辑更新:我尝试了一些不同的方法,但仍然遇到同样的问题。我开始测试打印 TimeInterval 和格式化字符串以比较并查看有什么问题。这绝对是某种舍入误差。

Total - 173.50678288936615 / 02:54
Step - 39.00026595592499 / 00:39
Total - 172.5073879957199 / 02:53
Step - 38.00087106227875 / 00:38
Total - 171.1903439760208 / 02:51
Step - 36.68382704257965 / 00:37
Total - 170.19031596183777 / 02:50
Step - 35.683799028396606 / 00:36

如您所见,总时间从 2:53 跳到 2:51,但步数计时器保持一致。原因是总时间间隔从四舍五入的 172.5 到四舍五入的 171.19。

我还看到计时器在没有暂停的情况下倒计时,并且它可靠地保持同步。所以我把它缩小到我的暂停代码。

解决了我的问题并张贴在这里供后代使用。我最终创建了 totalTimeLeftcurrentInterval 全局属性。然后,在暂停和恢复时,我没有跟踪暂停时间并将其添加到 endTime,而是使用仍然从上次 [=15= 存储的 totalTimeLeftcurrentInterval 值] 射击和做 endTime = Date().addingTimeInterval(totalTimeLeft) 与间隔时间相同。这消除了暂停时间添加会弄乱四舍五入的奇怪数量。