Format the real-time stopwatch timer to the hundredth using Swift

I have an application using NSTimer in centisecond (0.01 seconds) to display the current stopwatch in String Format as 00: 00.00 (mm: ss.SS). (Basically, cloning the iOS built-in stopwatch to integrate into real-time math problems with math time, perhaps in the future, accuracy in milliseconds is required)

I am using (misuse?) NSTimer to force an update to UILabel. If the user clicks the Start button, this is the NSTimer code used to start repeating the function:

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

And here is the function executed by the above 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 } } 

At the same time, at least 2 displayed links will work. Will this method be too inefficient when all other elements will play?

The display is then updated without using NSTimer when the user presses stop / pause / reset. I did not find anything that directly translated into Swift. I am pretty sure that I am using an inefficient method to quickly update UILabel text in a UIView.

More: I am working on less messy code for the start timer format (mm: ss.SS). I will update it again when we finish this.

UPDATE: Thanks to Rob and jtbandes for answering both of my questions (formatting method and display mapping method). It was easy to replace NSTimer (see above) with CADisplayLink ():

 displayLink = CADisplayLink(target: self, selector: Selector("display")) displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes) 

And then replace all code instances

 displayOnlyTimer.invalidate() 

with

 displayLink.paused = true 

(this will pause the display of the image link)

+5
source share
2 answers

To quickly update the user interface, you must use CADisplayLink . Anything faster than the display refresh rate is a waste of processing power because it is physically impossible to display. It also provides the timestamp previous frame, so you can try to predict when the next frame will be.

You count CACurrentMediaTime() - timerStarted + elapsedTime several times. I would recommend doing this only once and storing it in a local variable.

Consider using NSDateComponentsFormatter . Try reusing one formatting instance, rather than creating a new one each time (which is usually the most expensive part). In general, the less string manipulation you can do, the better.

You can check CACurrentMediaTime at the beginning and end of your display method to see how long it takes. Ideally, this should be much less than 16.6 ms. Watch for CPU usage (and overall power consumption) in the Xcode debug navigator.

+6
source

Today I solved the same problem and found this answer. Rob and jtbandes' tips helped a lot and I was able to put together clean and working solutions from the Internet. Thank you guys. And thanks mothy for the question.

I decided to use CADisplayLink because there is no point in calling the timer timer more often than updating the screen:

 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) //... } //... } 

I track time by incrementing the elapsedTime variable by displayLink.duration every checkmark:

 var elapsedTime: CFTimeInterval! override init() { //... self.elapsedTime = 0.0 //... } func tick(sender: CADisplayLink) { elapsedTime = elapsedTime + displayLink.duration //... } 

Time formatting is done through NSDateFormatter:

 private let formatter = NSDateFormatter() override init() { //... formatter.dateFormat = "mm:ss,SS" } func elapsedTimeAsString() -> String { return formatter.stringFromDate(NSDate(timeIntervalSinceReferenceDate: elapsedTime)) } 

The user interface can be updated by closing the callback that the stopwatch calls every tick:

 var callback: (() -> Void)? func tick(sender: CADisplayLink) { elapsedTime = elapsedTime + displayLink.duration // Calling the callback function if available callback?() } 

And all you have to do in ViewController to use the stopwatch:

 let stopwatch = Stopwatch() stopwatch.callback = self.tick func tick() { elapsedTimeLabel.text = stopwatch.elapsedTimeAsString() } 

Here's the gist with the complete Stopwatch code and usage guide: https://gist.github.com/Flar49/06b8c9894458a3ff1b14

I hope that this explanation and essence will help others who in the future will stumble with this thread with the same problem :)

+1
source

All Articles