Swift Timer()麻烦

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift Timer()麻烦相关的知识,希望对你有一定的参考价值。

我在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)"
    }
}
答案

您应该将秒计算为:

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.01totalSeconds来追踪时间。计时器不准确。你的时钟会随着时间而漂移。当你启动计时器并获得Date()中的当前时间戳Date())并获得两者之间的差异时,最好保存时间戳(updateTimer)。

另一答案

这是一个定时器函数,输出格式分钟:秒:毫秒,与你的代码比较,你会发现你的代码有什么问题。

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)"
    }
}
另一答案

要从浮点总秒数中获得分钟和秒数,elapsed您可以:

  1. 要获得分钟,除以60.0并截断为最接近的整数: let minutes = Int(elapsed / 60)
  2. 要获得秒数,请通过以下方式获取余数: let seconds = elapsed - Double(minutes) * 60 要么 let seconds = elapsed.truncatingRemainder(dividingBy: 60)

其他几点意见:

  1. 当屏幕刷新率通常限制在每秒60帧时,每0.01秒运行一次计时器是没有意义的。如果要以最大频率更新它,请使用CADisplayLink,它不仅可以获得最大屏幕刷新率,还可以最佳激发,以便在下一帧渲染之前进行更新。
  2. 您不应该使用计时器将经过的时间增加0.01(或任何固定的间隔),因为您无法保证它将实际以该频率触发。例如,如果某个东西暂时阻塞主线程200毫秒,则不希望这会影响您对已经过的时间量的计算。 相反,保存计时器启动时的开始时间,每次计时器触发时重新计算已用时间并相应地格式化结果。
  3. 为了进一步复杂化,你甚至不应该比较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()
}

以上是关于Swift Timer()麻烦的主要内容,如果未能解决你的问题,请参考以下文章

使用 Swift Combine 创建一个 Timer Publisher

swift开发之 -- 自动轮播图(UIScrollView+UIPageControl+Timer)

swift常用代码片段

swift 代码片段

如何将这个 Objective-C 代码片段写入 Swift?

Swift异步序列构造器AsyncStream内部定时器(Timer)无法被触发的解决