How can I programmatically pause an NSTimer?

Viewed 44524

I'm using an NSTimer to do some rendering in an OpenGL based iPhone app. I have a modal dialog box that pops up and requests user input. While the user is providing input I'd like to "pause" i.e. something like this:

[myNSTimer pause];

I'm using this syntax because I've been doing things like:

[myNSTimer invalidate];

when I want it to stop.

How can I programmatically pause the NSTimer?

16 Answers

From here:

http://discussions.apple.com/thread.jspa?threadID=1811475&tstart=75

"You can store the amount of time that has passed since the timer started... When the timer starts store the date in an NSDate variable. Then when the user switches... use the method timeIntervalSinceNow in the NSDate class to store how much time has passed... note that this will give a negative value for timeIntervalSinceNow. When the user returns use that value to set an appropriate timer.

I don't think there's a way to pause and restart a timer. I faced a similar situation. "

In my case, I had a variable 'seconds', and another 'timerIsEnabled'. When I wanted to pause the timer, just made the timerIsEnabled as false. Since the seconds variable was only incremented if timerIsEnabled was true, I fixed my problem. Hope this helps.

You cant pause NSTimer as mentioned above. So the time to be captured should not be dependent on Timer firedate ,i suggest. Here is my simplest solution :

When creating the timer initialize the starting unit time like:

self.startDate=[NSDate date];
self.timeElapsedInterval=[[NSDate date] timeIntervalSinceDate:self.startDate];//This ``will be 0 //second at the start of the timer.``     

myTimer= [NSTimer scheduledTimerWithTimeInterval:1.0 target:self `selector:@selector(updateTimer) userInfo:nil repeats:YES];

` Now in the update timer method:

  NSTimeInterval unitTime=1;
-(void) updateTimer
 {
 if(self.timerPaused)
       {
           //Do nothing as timer is paused
       }
 else{
     self.timerElapsedInterval=timerElapsedInterval+unitInterval;
    //Then do your thing update the visual timer texfield or something.
    }

 }

Well, I find this as the best method to implement the pausing a timer thing. Use a static variable to toggle pause and resume. In pause, invalidate the timer and set it to nil and in the resume section reset the timer using the original code by which it was started.

The following code works on the response to a pause button click.

-(IBAction)Pause:(id)sender
{
    static BOOL *PauseToggle;
    if(!PauseToggle)
    {
        [timer invalidate];
        timer = nil;
        PauseToggle = (BOOL *) YES;
    }
    else
    {
        timer = [NSTimer scheduledTimerWithTimeInterval:0.04 target:self selector:@selector(HeliMove) userInfo:nil repeats:YES];
        PauseToggle = (BOOL *) NO;
    }
}

Here is a more modern solution, wrapping the timer and storing when it stopped.

I also ran into an interesting bug when invoking the pause in the actual timer fire callback, which will cause the timer to keep firing repeatedly after the expression timer.fireDate.timeIntervalSinceNow evaluates to zero, I added a flag in the pause to explicitly avoid this case

import Foundation

class PausableTimer {
    
    let timer: Timer
    var timeIntervalOnPause: TimeInterval?
        
    init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) {
        timer = Timer(timeInterval: interval, repeats: repeats, block: block)
    }
    
    func invalidate() {
        timer.invalidate()
    }
    
    var paused: Bool {
        timeIntervalOnPause != nil
    }
    
    func toggle() {
        if paused {
            resume()
        } else {
            pause(isFiringTimer: false)
        }
    }
    
    /// Pause the timer
    /// - Parameter isFiringTimer: pass in true if the timer is currently firing, in which case the resume time will not
    /// be relative to next fire date, but instead relative to the time interval. It's improtant that this be correct otherwise there maybe a near
    /// infinite loop firing of the `Timer`
    func pause(isFiringTimer: Bool) {
        guard timer.isValid else {
            return
        }
        timeIntervalOnPause = isFiringTimer || timer.fireDate.timeIntervalSinceReferenceDate == 0 ? timer.timeInterval : timer.fireDate.timeIntervalSinceNow
        timer.fireDate = .distantFuture
    }
    
    /// Resume the timer, if it was not paused has not effect, and causes assertion failure
    func resume() {
        guard timer.isValid else {
            return
        }
        guard let timeIntervalOnPause = timeIntervalOnPause else {
            assertionFailure("Resuming a timer that was never paused \(self) - \(self.timer)")
            return
        }
        let relativeFireDate = Date(timeIntervalSinceNow: timeIntervalOnPause)
        timer.fireDate = relativeFireDate
        self.timeIntervalOnPause = nil
    }
    
}
Related