8

I want to stop this timer and then restart it from where I stopped it.

secondsTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(addSeconds), userInfo: nil, repeats: true)

Below, it was suggested I shouldn't increment a timer in my timer handler. Why not?

For example, using GCD timer:

func countSeconds() {

    secondsTimer = DispatchSource.makeTimerSource(queue: .main)
    secondsTimer?.schedule(deadline: .now(), repeating: 1.0)

    secondsTimer?.setEventHandler { [weak self] in

        self?.addSeconds()
    }
}

@objc func addSeconds() {
    seconds += 1                         
}

func startGame() {  
    secondsTimer?.resume()
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
Pietro P.
  • 185
  • 1
  • 1
  • 7

4 Answers4

8

We don't pause/resume Timer instances. We stop them with invalidate(). And when you want to restart it, just create new timer.

Please refer to the Timer documentation, also available right in Xcode.


Note that you can suspend and resume GCD timers, DispatchSourceTimer.

var timer: DispatchSourceTimer?  // note, unlike `Timer`, we have to maintain strong reference to GCD timer sources

func createTimer() {
    timer = DispatchSource.makeTimerSource(queue: .main)
    timer?.schedule(deadline: .now(), repeating: 1.0)

    timer?.setEventHandler { [weak self] in      // assuming you're referencing `self` in here, use `weak` to avoid strong reference cycles
        // do something
    }

    // note, timer is not yet started; you have to call `timer?.resume()`
}

func startTimer() {
    timer?.resume()
}

func pauseTiemr() {
    timer?.suspend()
}

func stopTimer() {
    timer?.cancel()
    timer = nil
}

Please note, I am not suggesting that if you want suspend and resume that you should use GCD DispatchSourceTimer. Calling invalidate and recreating Timer as needed is simple enough, so just do that. I only provide this GCD information for the sake of completeness.


By the way, as a general principle, never "increment" some counter in your timer handler. That's a common mistake. Timers are not guaranteed to fire every time or with exact precision. Always save some reference time at the start, and then in your event handler, calculate differences between the current time and the start time. For example, extending my GCD timer example:

func createTimer() {
    timer = DispatchSource.makeTimerSource(queue: .main)
    timer?.schedule(deadline: .now(), repeating: 0.1)

    let formatter = DateComponentsFormatter()
    formatter.unitsStyle = .positional
    formatter.allowedUnits = [.hour, .minute, .second, .nanosecond]
    formatter.zeroFormattingBehavior = .pad

    timer?.setEventHandler { [weak self] in
        guard let start = self?.start else { return }
        let elapsed = (self?.totalElapsed ?? 0) + CACurrentMediaTime() - start
        self?.label.text = formatter.string(from: elapsed)
    }
}

var start: CFTimeInterval?         // if nil, timer not running
var totalElapsed: CFTimeInterval?

@objc func didTapButton(_ button: UIButton) {
    if start == nil {
        startTimer()
    } else {
        pauseTimer()
    }
}

private func startTimer() {
    start = CACurrentMediaTime()
    timer?.resume()
}

private func pauseTimer() {
    timer?.suspend()
    totalElapsed = (totalElapsed ?? 0) + (CACurrentMediaTime() - start!)
    start = nil
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • And how can I create a seconds counter where I can stop and resume it ? Thank a lot – Pietro P. Oct 17 '17 at 15:56
  • Capture the start time (`CACurrentMediaTime()` or `CFAbsoluteTimeGetCurrent()`) when you start a timer and when you pause the timer, calculate the elapsed between `CACurrentMediaTime()` or `CFAbsoluteTimeGetCurrent()` and the saved start time. – Rob Oct 17 '17 at 16:03
  • @PietroP. - Re my point of not incrementing your timer, the idea is that you have no assurances that it will be called with the frequency you expect. Generally it will be, but not always. See revised answer with an example that avoids incrementing timer like you are. – Rob Oct 17 '17 at 17:03
  • If we can't reset a DispatchSourceTimer how can we handle injected Timers? Resetting in this way would reset a mock in to the real deal. – Declan McKenna Jun 18 '19 at 09:36
  • You can `suspend` and `resume` dispatch timers, so I’m not sure what you mean by “can’t reset”. Your question seems sufficiently involved (where we’d want to see a [MCVE](https://stackoverflow.com/help/MCVE) example of what you’re trying to do) that you should just post your own question rather than continuing discussion in the comments here. – Rob Jun 18 '19 at 14:51
3

I do it with this code:

var timer: Timer?

func startTimer() {
    timer = .scheduledTimer(withTimeInterval: 4, repeats: false, block: { _ in
        // Do whatever
    })
}

func resetTimer() {
    timer?.invalidate()
    startTimer()
}
Display Name
  • 4,502
  • 2
  • 47
  • 63
0

You can start, stop and reset timer in swift4

   class ViewController: UIViewController {

 var counter = 0
 var timer = Timer()
 var totalSecond = 20


@IBOutlet weak var label1: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.


    }





@IBAction func start_btn(_ sender: Any) {



    timer.invalidate() // just in case this button is tapped multiple times


    timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)

}






@IBAction func stop_btn(_ sender: Any) {





    do {
        self.timer.invalidate()
    }

    func timeFormatted(_ totalSeconds: Int) -> String {
        let seconds: Int = totalSeconds % 60
        return String(format: "0:%02d", seconds)
    }

    }



  @IBAction func reset_btn(_ sender: Any) {


    timer.invalidate()
   //timerAction()
    counter = 0
    label1.text = "\(counter)"


}



@objc func timerAction()


{
    counter += 1
    label1.text = "\(counter)"
}

}
-3

You can declare the Timer as 'weak var' instead of just 'var' like:

weak var timer: Timer?

Now you can pause your timer with:

timer?.invalidate()

To resume:

timer?.fire()
S.S.D
  • 1,579
  • 2
  • 12
  • 23
  • If your timer is `weak` and you `invalidate` it, it will be deallocated. And even if you kept strong reference, `fire` will not restart an invalidated timer. And even if it did, `fire` triggers it to fire immediately, not according to when it was scheduled to fire on the run loop. But, as the `invalidate` documentation says, it “stops the timer from ever firing again”, so this is moot. – Rob Aug 01 '19 at 15:45