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

时间:2015-07-13 04:19:11

标签: swift nstimer stopwatch time-format cadisplaylink

我有一个使用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个显示链接同时运行。一旦所有其他元素都在发挥作用,这种方法效率是否太低?

当用户按下停止/暂停/重置时,不使用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 

(这将暂停更新显示链接)

2 个答案:

答案 0 :(得分:6)

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

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

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

您可以在显示方法的开头和结尾检查CACurrentMediaTime,看看它需要多长时间。理想情况下,它应该小于16.6ms。请关注Xcode调试导航器中的CPU使用率(以及一般功耗)。

答案 1 :(得分:1)

我今天解决了同样的问题并找到了答案。 Rob和jtbandes&#39;建议得到了很多帮助,我能够从互联网上组装清洁和工作的解决方案。谢谢你们。感谢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)
        //...
    }
    //...
}

我通过displayLink.duration每次打勾增加elapsedTime变量来跟踪时间:

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可以在回调闭包中更新,Stopwatch会在每个刻度上调用:

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()
}

以下是使用秒表和使用指南的完整代码的要点: https://gist.github.com/Flar49/06b8c9894458a3ff1b14

我希望这个解释和要点能够帮助那些将来遇到同样问题的人偶然发现这个问题:)