Swift Timer()麻烦

时间:2018-01-01 21:18:22

标签: ios 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)"
    }
}

3 个答案:

答案 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您可以:

  1. 要获得分钟数,除以60.0并截断为最接近的整数:

    let minutes = Int(elapsed / 60)
    
  2. 要获得秒数,请通过以下方式获取余数:

    let seconds = elapsed - Double(minutes) * 60
    

    或者

    let seconds = elapsed.truncatingRemainder(dividingBy: 60)
    
  3. 其他一些观察结果:

    1. 当屏幕刷新率通常限制在每秒60帧时,每0.01秒运行一次计时器是没有意义的。如果您想以最高频率更新它,请使用CADisplayLink,它不仅可以获得最大屏幕刷新率,还可以最佳激发,以便在下一帧渲染之前进行更新。

    2. 您不应该使用计时器将经过时间增加0.01(或任何固定间隔),因为您无法保证它实际上会以该频率触发。例如,如果某些东西暂时阻塞主线程200毫秒,那么您不希望这会影响您对已经过的时间量的计算。

      相反,保存计时器启动时的开始时间,每次计时器触发时重新计算经过的时间并相应地格式化结果。

    3. 为了进一步复杂化,您甚至不应该比较Date()个实例(或CFAbsoluteTimeGetCurrent()值),因为the documentation warns us

        

      重复调用此函数并不能保证单调增加结果。由于与外部时间参考同步或由于时钟的明确用户更改,系统时间可能会减少。

      相反,您应该使用基于mach_absolute_time的计算(例如CACurrentMediaTime()返回的计算),确保重复调用可以准确地返回经过时间的计算。

      如果您的应用程序将开始时间保存在持久存储中,则应该使用Date()CFAbsoluteTimeGetCurrent(),以便稍后在应用程序重新启动时(可能在设备重新启动后)进行检索以进行呈现应用程序启动之间经过的时间的影响。但这是一个相当狭窄的边缘案例。

    4. 无论如何,这会产生:

      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()
      }
      
相关问题