暂停后显示倒计时的标签有时会不同步。舍入误差?
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。
我还看到计时器在没有暂停的情况下倒计时,并且它可靠地保持同步。所以我把它缩小到我的暂停代码。
解决了我的问题并张贴在这里供后代使用。我最终创建了 totalTimeLeft
和 currentInterval
全局属性。然后,在暂停和恢复时,我没有跟踪暂停时间并将其添加到 endTime
,而是使用仍然从上次 [=15= 存储的 totalTimeLeft
和 currentInterval
值] 射击和做 endTime = Date().addingTimeInterval(totalTimeLeft)
与间隔时间相同。这消除了暂停时间添加会弄乱四舍五入的奇怪数量。
我有一个使用 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。
我还看到计时器在没有暂停的情况下倒计时,并且它可靠地保持同步。所以我把它缩小到我的暂停代码。
解决了我的问题并张贴在这里供后代使用。我最终创建了 totalTimeLeft
和 currentInterval
全局属性。然后,在暂停和恢复时,我没有跟踪暂停时间并将其添加到 endTime
,而是使用仍然从上次 [=15= 存储的 totalTimeLeft
和 currentInterval
值] 射击和做 endTime = Date().addingTimeInterval(totalTimeLeft)
与间隔时间相同。这消除了暂停时间添加会弄乱四舍五入的奇怪数量。