11

I want to change the timer every millisecond but it doesnt work as expected.

NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector: Selector("advanceTimer:"), userInfo: nil, repeats: true);

func advanceTimer(timer: NSTimer){

    self.time += 0.001;

    let milliseconds = self.time * 100;
    let remaingMilliseconds = Int((milliseconds % 1000) / 10);
    let seconds = Int((milliseconds / 1000) % 60)

    let strSeconds = String(format: "%02d", seconds)
    let strFraction = String(format: "%02d", remaingMilliseconds)

    timerText.text = "\(strSeconds):\(strFraction)";

 }

The result is

The timer change up to 100 in the millisecond part (00:100) and then change to 01:00 = 40 real secs

Duncan approach:

var time: NSTimeInterval = 0;
var startTime: NSTimeInterval = 0;

//And your  timer method...
func advanceTimer(timer: NSTimer){
    //Total time since timer started, in seconds
    self.time = NSDate.timeIntervalSinceReferenceDate() - startTime
    println(self.time);
    //The rest of your code goes here
}

override func didMoveToView(view: SKView) {

    // Set anchor point
    self.anchorPoint = CGPointMake(0.5, 0.5);


    var startTime: NSTimeInterval
    //Sart the timer
    startTime = NSDate.timeIntervalSinceReferenceDate()
    NSTimer.scheduledTimerWithTimeInterval(0.02,
            target: self,
            selector: Selector("advanceTimer:"),
            userInfo: nil,
            repeats: true)
}

Result: 456680125.54539 the first print

grape1
  • 759
  • 2
  • 8
  • 19
  • The code seems okay, so the problem must be in your expectactions. What do you expect the code to do? I suppose the timer grows slower that you would expect? – Sulthan Jun 22 '15 at 14:53
  • 4
    The resolution of NSTimer is limited to 50-100 milliseconds. Compare for example http://stackoverflow.com/questions/30945310/countdown-with-several-decimal-slots-using-nstimer-in-swift. – Your problem description *"it doesnt work as expected"* is not helpful at all. – Martin R Jun 22 '15 at 14:56
  • Look into [CADisplayLink](https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/) if you only need to make UI updates due to the timer firing. – Mats Jun 22 '15 at 15:00
  • 2
    @Martin An unqualified "It doesn't work" is probably my least favorite thing to read in an SO question. It's hard to get LESS helpful than that. – Duncan C Jun 22 '15 at 15:08
  • @grape1: Your time calculations also seem to be wrong (why multiply with 100??). You should check some examples with a "paper and pen" calculation. Note also that you need *three* decimal digits to print milliseconds. – Martin R Jun 22 '15 at 15:18
  • http://stackoverflow.com/a/30653322/2303865 – Leo Dabus Jun 22 '15 at 16:45
  • In your "Duncan approach" code, you have 2 variables `startTime`, one at global scope and one inside your `didMoveToView()` function. That's wrong. get rid of the local variable `startTime` inside `didMoveToView()`. – Duncan C Nov 11 '16 at 16:08

1 Answers1

17

As Martin says in his comment, timers have a resolution of 50-100 ms (0.05 to 0.1 seconds). Trying to run a timer with an interval shorter than that will not give reliable results. Also, timers are not realtime. They depend on the run loop they are attached to, and if the run loop gets busy, the firing of the timer gets delays.

Instead of trying to increment a counter each time your timer fires, record the start time when you initiate the timer, and then do some math to figure out how much time has transpired:

var startTime: NSTimeInterval

//Sart the timer
startTime = NSDate.timeIntervalSinceReferenceDate()
NSTimer.scheduledTimerWithTimeInterval(0.02, 
  target: self, 
  selector: Selector("advanceTimer:"), 
  userInfo: nil,
  repeats: true)


//And your  timer method...
func advanceTimer(timer: NSTimer)
{
  //Total time since timer started, in seconds
  self.time = NSDate.timeIntervalSinceReferenceDate() - startTime
  //The rest of your code goes here
 }

EDIT:

The Swift 3 version of this code looks like this:

(Written as a view controller in a test project)

class ViewController: UIViewController {

  weak var timer: Timer?
  var startTime: Double = 0
  var time: Double = 0

  @IBOutlet weak var timeValueLabel: UILabel!

  /* 
  When the view controller first appears, record the time and start a timer
  */
  override func viewDidAppear(_ animated: Bool) {
    startTime = Date().timeIntervalSinceReferenceDate
    timer = Timer.scheduledTimer(timeInterval: 0.05,
                                 target: self,
                                 selector: #selector(advanceTimer(timer:)),
                                 userInfo: nil,
                                 repeats: true)
  }

  //When the view controller is about to disappear, invalidate the timer
  override func viewWillDisappear(_ animated: Bool) {
    timer?.invalidate()
  }


  func advanceTimer(timer: Timer) {

    //Total time since timer started, in seconds
    time = Date().timeIntervalSinceReferenceDate - startTime

    //The rest of your code goes here

    //Convert the time to a string with 2 decimal places
    let timeString = String(format: "%.2f", time)

    //Display the time string to a label in our view controller
    timeValueLabel.text = timeString
  }
}
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • If i print self.time it shows "456679230.421599" after 1 second – grape1 Jun 22 '15 at 15:23
  • Edit your question to show your new code. There must be a bug somewhere. – Duncan C Jun 22 '15 at 15:31
  • Your code is wrong. Inside your didMoveToView function you declare a new local variable startTime. Get rid of the word "var" there so you are changing the instance variable startTime. – Duncan C Jun 22 '15 at 16:00
  • Thanks, but how to pause the timer and then started from there? – grape1 Jun 23 '15 at 07:20
  • 1
    You can't pause an NSTimer. You have to invalidate the timer, note the amount of time that has passed, and then start a new timer. – Duncan C Jun 23 '15 at 10:52
  • Can you give an example – grape1 Jun 23 '15 at 11:38
  • And I don't want to pause the NSTimer, but to pause self.time = NSDate.timeIntervalSinceReferenceDate() - startTime; – grape1 Jun 23 '15 at 12:00
  • @DuncanC I would really appreciate it if you could please update the code for Swift 3.0 also. I am trying to use your code but running into a number of issues. – iGetIt Nov 11 '16 at 14:03
  • @iGetIt, you should post a new question. – Duncan C Nov 11 '16 at 16:09
  • is there any way real time timer ?, mean i want to run Timer more than 0.05, is it possible ? – Sultan Ali Oct 09 '20 at 13:14
  • You can use a CADisplayLink timer, which is tied to the screen refresh. I believe the screen refresh rate is 60 times a second (1/60th of a second, or 0.016666 seconds) – Duncan C Oct 09 '20 at 14:22