Countdown with several decimal slots, using NSTimer in Swift
Asked Answered
J

2

3

I want to make an app that has a timer starting at 10.0000000 for example, and I want it to countdown perfectly Here's my code so far:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var labelTime: UILabel!

    var counter = 10.0000000

    var labelValue: Double {
        get {
            return NSNumberFormatter().numberFromString(labelTime.text!)!.doubleValue
        }
        set {
            labelTime.text = "\(newValue)"
        }
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        labelValue = counter
        var timer = NSTimer.scheduledTimerWithTimeInterval(0.0000001, target: self, selector: ("update"), userInfo: nil, repeats: true)
    }

    func update(){
        labelValue -= 0.0000001
    }


}

What happens is that my countdown is really slow, it's just not working and it would take like 1 hour to get to 0 seconds, instead of just 10 seconds. Any ideas? What changes should I make to my code? Thanks

Juback answered 19/6, 2015 at 18:51 Comment(1)
Could you please edit and split your code in several lines. Hart to read (and understand) when it's on a single line. Or is that common practice with swift ?Collapse
D
6

Timers are not super-accurate, and the resolution of NSTimer is about 1/50th of a second.

Plus, the refresh rate of the iPhone screen is 60 frames/second, so it's totally pointless to run your timer any faster than that.

Rather than trying to use a timer to decrement something every time it fires, create a timer that fires like 50 times a second, and have it use clock math to update the display based on the remaining time:

var futureTime: NSTimeInterval 

override func viewDidLoad() {
    super.viewDidLoad()
    labelValue = counter

    //FutureTime is a value 10 seconds in the future.
    futureTime = NSDate.timeIntervalSinceReferenceDate() + 10.0 

    var timer = NSTimer.scheduledTimerWithTimeInterval(
      0.02, 
      target: self, 
      selector: ("update:"), 
      userInfo: nil, 
      repeats: true)
}

func update(timer: NSTimer)
{
  let timeRemaining = futureTime - NSDate.timeIntervalSinceReferenceDate()
  if timeRemaining > 0.0
  {
    label.text = String(format: "%.07f", timeRemaining)
  }
  else
  {
    timer.invalidate()
    //Force the label to 0.0000000 at the end
    label.text = String(format: "%.07f", 0.0)
  }
}
Disclaim answered 19/6, 2015 at 19:5 Comment(11)
I edited my sample code slightly. See the updated version.Disclaim
this works great! Thank you! In swift, how can I make my float only have 7 decimal cases? Seems as if it doesn't work with the @"%.07f"Rackley
What does it give you? "It doesn't work" is incredibly, maddeningly UN-helpful.Disclaim
Ahaha sorry. It is working now, had to remove the '@'Rackley
Oh, sorry. That's an Objective-C habit that is hard to break. (Fixed)Disclaim
Hey sorry to bother you, but I was wondering how could I raise the precision of the timer countdown? I'm performing a println and it's not going through all the slots when I have 3 decimal slots, and I want it to go through EVERY number... How? PLEASE help I'm desperateRackley
It isn't clear what you want to accomplish. Desperate to do what, exactly? The debug console (where println output goes) is actually quite slow. It can't display a message every 1/1000th of a second. If you want a timer that counts every 0.001 seconds, that's thousandths of a second. You simply can't do that.Disclaim
You could create a high priority task in GCD that runs on a separate thread and takes some action every thousandth of a second, but anything that interacts with the screen simply can't keep up. The screen only refreshes once every 1/60th of a second. You can't update the screen faster than that under any circumstances.Disclaim
I have a game where I have a timer that starts at 5.000 and it starts counting down infinitely. The objective of the game is for the user to stop the timer at 0.000. I don't necessarily need for the screen to update EVERY mili-mili-mili second, all I want is for when I press the button to stop the timer, for it to show me THE EXACT result! And right now it isn't doing that... Is this possible? Thank youRackley
Post your button IBAction code. That's where you need to calculate your actual time remaining. You should model your code after the code I posted above.Disclaim
BTW, you should use Double, not Float, to do math on time intervals.Disclaim
N
2

Are you trying to make it display every combination between 0.0000001 and .99999999 in a period of one second? The screen would literally have to update a hundred million times in order to display every number. There's no feasible way to do this in a single second on any present technology or likely any future technology. The screen itself cannot update any faster than 60 times a second, so that's the fastest this will work for you.

You can try using NSTimer for that rate (1/60 = 0.0167). NSTimer itself isn't guaranteed to be very precise. In order to update the screen at every frame, you'll have to use CADisplayLink (https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/).

This gives you the chance to run a selector on every frame update, which is as fast as the system can change the frame by definition.

Negrillo answered 19/6, 2015 at 19:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.