我在Swift中做了一个简单的计时器。当秒数达到59秒时,一切都很好。而不是回到零,他们只是继续前进。有人能够指出我出错的地方以及为什么会这样吗?
@IBAction func startButtonDidTouch(_ sender: Any) {
if !timerIsRunning{
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
timerIsRunning = true
}
}
@objc func updateTimer() {
totalSeconds += 0.01
let totalSecondsTimes100: Int = Int(totalSeconds*100)
let minutes = Int(totalSeconds/60)
let timerChoice = Double(minutes)
let minStr = (minutes == 0) ? "00" : "0\(minutes)"
let secStr = (totalSeconds < 9) ? "0\(Float(totalSecondsTimes100)/100)" : "\(Float(totalSecondsTimes100)/100)"
switch Int(timerChoice) {
case Int(timerCountdownLabel.text!)!:
timerLabel.text = "\(minStr):\(secStr)"
audioPlayer.play()
timer.invalidate()
timerIsRunning = false
default:
timerLabel.text = "\(minStr):\(secStr)"
}
}
答案 0 :(得分:2)
您应该将秒数计算为:
let seconds = totalSeconds % 60
,然后在计算seconds
时使用secStr
,而不是使用totalSeconds
。
有更好的方法来编写代码:
@objc func updateTimer() {
totalSeconds += 0.01
let minutes = Int(totalSeconds) / 60
let seconds = totalSeconds.remainder(dividingBy: 60)
let timeStr = String(format: "%02d:%06.3f", minutes, seconds)
timerLabel.text = timeStr
if Int(timerCountdonwLabel.text!)! == minutes {
audioPlayer.play()
timer.invalidate()
timerIsRunning = false
}
}
你真的不应该只是将0.01
添加到totalSeconds
来跟踪时间。计时器不准确。你的时钟会随着时间而漂移。最好在启动计时器时保存时间戳(Date()
)并获取Date()
内的当前时间戳(updateTimer
)并获得两者之间的差异。
答案 1 :(得分:0)
这是一个定时器功能,输出格式分钟:秒:毫秒,与你的代码比较,你会发现你的代码有什么问题。
private weak var timer: Timer?
private var startTime: Double = 0
private var elapsed: Double = 0
private var time: Double = 0
private func startTimer(){
startTime = Date().timeIntervalSinceReferenceDate - elapsed
timer = Timer.scheduledTimer(timeInterval: (0.01), target: self, selector: #selector(updateTimeLabel), userInfo: nil, repeats: true)
}
private func stopTimer(){
elapsed = Date().timeIntervalSinceReferenceDate - startTime
timer?.invalidate()
}
@objc func updateTimeLabel(){
time = Date().timeIntervalSinceReferenceDate - startTime
let minutes = UInt8(time / 60.0)
let timeNoMin = time - (TimeInterval(minutes) * 60)
let seconds = UInt8(timeNoMin)
let timeNoSec = timeNoMin - (TimeInterval(seconds))
let milliseconds = UInt16(timeNoSec * 100)
let strMinutes = String(minutes)
var strSeconds = ""
if strMinutes == "0" {
strSeconds = String(seconds)
}
else {
strSeconds = String(format: "%02d", seconds)
}
let strMilliseconds = String(format: "%02d"), milliseconds)
if strMinutes != "0" {
timerLabel.text = "\(strMinutes):\(strSeconds).\(strMilliseconds)"
}
else {
timerLabel.text = "\(strSeconds).\(strMilliseconds)"
}
}
答案 2 :(得分:0)
要从经过的浮点总秒数获得分钟和秒数,elapsed
您可以:
要获得分钟数,除以60.0并截断为最接近的整数:
let minutes = Int(elapsed / 60)
要获得秒数,请通过以下方式获取余数:
let seconds = elapsed - Double(minutes) * 60
或者
let seconds = elapsed.truncatingRemainder(dividingBy: 60)
其他一些观察结果:
当屏幕刷新率通常限制在每秒60帧时,每0.01秒运行一次计时器是没有意义的。如果您想以最高频率更新它,请使用CADisplayLink
,它不仅可以获得最大屏幕刷新率,还可以最佳激发,以便在下一帧渲染之前进行更新。
您不应该使用计时器将经过时间增加0.01(或任何固定间隔),因为您无法保证它实际上会以该频率触发。例如,如果某些东西暂时阻塞主线程200毫秒,那么您不希望这会影响您对已经过的时间量的计算。
相反,保存计时器启动时的开始时间,每次计时器触发时重新计算经过的时间并相应地格式化结果。
为了进一步复杂化,您甚至不应该比较Date()
个实例(或CFAbsoluteTimeGetCurrent()
值),因为the documentation warns us:
重复调用此函数并不能保证单调增加结果。由于与外部时间参考同步或由于时钟的明确用户更改,系统时间可能会减少。
相反,您应该使用基于mach_absolute_time
的计算(例如CACurrentMediaTime()
返回的计算),确保重复调用可以准确地返回经过时间的计算。
如果您的应用程序将开始时间保存在持久存储中,则应该使用Date()
或CFAbsoluteTimeGetCurrent()
,以便稍后在应用程序重新启动时(可能在设备重新启动后)进行检索以进行呈现应用程序启动之间经过的时间的影响。但这是一个相当狭窄的边缘案例。
无论如何,这会产生:
var start: CFTimeInterval?
weak var displayLink: CADisplayLink?
func startTimer() {
self.displayLink?.invalidate() // just in case timer had already been started
start = CACurrentMediaTime()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.preferredFramesPerSecond = 100 // in case you're using a device that can render more than 60 fps
displayLink.add(to: .main, forMode: .commonModes)
self.displayLink = displayLink
}
@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CACurrentMediaTime() - start!
let minutes = Int(elapsed / 60)
let seconds = elapsed - Double(minutes) * 60
let string = String(format: "%02d:%05.2f", minutes, seconds)
label.text = string
}
func stopTimer() {
displayLink?.invalidate()
}