使用 Swift 将实时秒表计时器格式化为百分之一

Format realtime stopwatch timer to the hundredth using Swift

我有一个应用程序使用 NSTimer 以厘秒(0.01 秒)更新间隔显示 运行 秒表,字符串格式为 00:00.00 (mm:ss.SS)。 (基本上克隆了iOS内置秒表以集成到实时运动计时数学问题中,将来可能需要毫秒精度)

我使用(误用?)NSTimer 来强制更新 UILabel。如果用户按下 Start,这是用于开始重复该功能的 NSTimer 代码:

displayOnlyTimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("display"), userInfo: nil, repeats: true)

下面是上面的 NSTimer 执行的函数:

func display() {
    let currentTime = CACurrentMediaTime() - timerStarted + elapsedTime

    if currentTime < 60 {
        timeDisplay.text = String(format: "%.2f", currentTime)
    }else if currentTime < 3600 {
        var minutes = String(format: "%00d", Int(currentTime/60))
        var seconds = String(format: "%05.2f", currentTime % 60)
        timeDisplay.text =  minutes + ":" + seconds
    }else {
        var hours = String(format: "%00d", Int(currentTime/3600))
        var minutes = String(format: "%02d", (Int(currentTime/60)-(Int(currentTime/3600)*60)))
        var seconds = String(format: "%05.2f", currentTime % 60)
        timeDisplay.text =  hours + ":" + minutes + ":" + seconds
    }
}

至少会同时显示2个links 运行。当所有其他元素都在起作用时,此方法是否效率太低?

当用户按下 stop/pause/reset 时,显示会在不使用 NSTimer 的情况下更新。我没有找到任何直接翻译成 Swift 的内容。我相当确定我正在使用一种低效的方法来强制快速更新 UIView 中的文本 UILabel。

更多详情: 我正在为 运行 计时器格式 (mm:ss.SS) 编写更简洁的代码。完成后我会再更新一次。

更新:感谢 Rob 和 jtbandes 回答了我的两个问题(格式化方法和显示更新方法)。 用 CADisplayLink():

替换 NSTimer(见上文)很容易
displayLink = CADisplayLink(target: self, selector: Selector("display"))
        displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)

然后替换

代码中的所有实例
displayOnlyTimer.invalidate() 

displayLink.paused = true 

(这将暂停显示 link 更新)

要快速 UI 更新,您应该使用 CADisplayLink。任何比显示刷新率快的东西都是对处理能力的浪费,因为它在物理上无法显示。它还提供了前一帧的 timestamp,因此您可以尝试预测下一帧的时间。

您正在多次计算 CACurrentMediaTime() - timerStarted + elapsedTime。我建议只做一次并将其保存在局部变量中。

考虑使用 NSDateComponentsFormatter。尝试重用格式化程序的一个实例,而不是每次都创建一个新实例(这通常是最昂贵的部分)。总的来说,字符串操作越少越好。

您可以在显示方法的开始和结束处检查 CACurrentMediaTime,以了解需要多长时间。理想情况下,它应该 much 小于 16.6ms。在 Xcode 调试导航器中密切关注 CPU 使用情况(和一般功耗)。

我今天正在解决同样的问题并找到了这个答案。 Rob 和 jtbandes 的建议很有帮助,我能够 assemble 从 Internet 上找到干净有效的解决方案。谢谢你们。感谢 mothy 的提问。

我决定使用 CADisplayLink,因为没有必要比屏幕更新更频繁地触发计时器的回调:

class Stopwatch: NSObject {
    private var displayLink: CADisplayLink!
    //...

    override init() {
        super.init()

        self.displayLink = CADisplayLink(target: self, selector: "tick:")
        displayLink.paused = true
        displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
        //...
    }
    //...
}

我通过每次滴答将 elapsedTime 变量递增 displayLink.duration 来跟踪时间:

var elapsedTime: CFTimeInterval!

override init() {
    //...
    self.elapsedTime = 0.0
    //...
}

func tick(sender: CADisplayLink) {
    elapsedTime = elapsedTime + displayLink.duration
    //...
}

时间格式化通过 NSDateFormatter 完成:

private let formatter = NSDateFormatter()

override init() {
//...
        formatter.dateFormat = "mm:ss,SS"
}

func elapsedTimeAsString() -> String {
        return formatter.stringFromDate(NSDate(timeIntervalSinceReferenceDate: elapsedTime))
}

UI 可以在秒表每次报价时调用的回调闭包中更新:

var callback: (() -> Void)?

func tick(sender: CADisplayLink) {
    elapsedTime = elapsedTime + displayLink.duration

    // Calling the callback function if available
    callback?()
}

这就是您在 ViewController 中使用秒表所需要做的全部工作:

let stopwatch = Stopwatch()
stopwatch.callback = self.tick

func tick() {
   elapsedTimeLabel.text = stopwatch.elapsedTimeAsString()
}

这里是 Stopwatch 的完整代码和使用指南的要点: https://gist.github.com/Flar49/06b8c9894458a3ff1b14

我希望这个解释和要点能帮助将来遇到同样问题的其他人:)