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
The code below is for use with a scrollview, that brings content on at regular intervals, and loops back to the front on completion, but the same logic can be used for any pause/restart button.
The timer fires on load (initiateFeatureTimer), and pauses for user-interaction (will begin dragging method), allowing the user to look at content for as long as she likes. When user lifts her finger (will end dragging) the featurePaused boolean is reset, but also, you have the option of adding in a little more of a delay.
This way, the scroller doesn't move off instantly on finger lift, it's more reactive to the user.
//set vars, fire first method on load.
featureDelay = 8; //int
featureInt = featureDelay-1; //int
featurePaused = false; //boolean
-(void)initiateFeatureTimer {
featureTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(moveFeatureScroller) userInfo:nil repeats:true];
[featureTimer fire];
}
-(void)moveFeatureScroller {
if (!featurePaused){ //check boolean
featureInt++;
if (featureInt == featureDelay){
featureInt = 0; //reset the feature int
/*//scroll logic
int nextPage = (featurePage + 1) * w;
if (featurePage == features.count-1){ nextPage = 0; }
[featureScroller setContentOffset:CGPointMake(nextPage, 0)]; */
}
}
}
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
featurePaused = true; //pause the timer
}
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
featurePaused = false; //restart the timer
featureInt = -3; //if you want to add an additional delay
}
Based off @Thiru and @kapesoftware's answers, I made an Objective-C category on NSTimer using Associated Objects for pausing and resuming the timer.
#import <objc/runtime.h>
@interface NSTimer (PausableTimer)
@property (nonatomic, retain) NSDate *pausedDate;
@property (nonatomic, retain) NSDate *nextFireDate;
-(void)pause;
-(void)resume;
@end
...
@implementation NSTimer (PausableTimer)
static char * kPausedDate = "pausedDate";
static char * kNextFireDate = "nextFireDate";
@dynamic pausedDate;
@dynamic nextFireDate;
-(void)pause {
self.pausedDate = [NSDate date];
self.nextFireDate = [self fireDate];
[self setFireDate:[NSDate distantFuture]];
}
-(void)resume
{
float pauseTime = -1*[self.pausedDate timeIntervalSinceNow];
[self setFireDate:[self.nextFireDate initWithTimeInterval:pauseTime sinceDate:self.nextFireDate]];
}
- (void)setPausedDate:(NSDate *)pausedDate
{
objc_setAssociatedObject(self, kPausedDate, pausedDate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSDate *)pausedDate
{
return objc_getAssociatedObject(self, kPausedDate);
}
- (void)setNextFireDate:(NSDate *)nextFireDate
{
objc_setAssociatedObject(self, kNextFireDate, nextFireDate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSDate *)nextFireDate
{
return objc_getAssociatedObject(self, kNextFireDate);
}
It keeps things tidy and means you don't have to create instance variables or properties just to pause and resume your timers.
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.
}
}
Just thought of updating minor fixes to kapesoftware's answer:
NSDate *pauseStart, *previousFireDate;
-(void) pauseTimer:(NSTimer *)timer {
pauseStart = [[NSDate dateWithTimeIntervalSinceNow:0] retain];
previousFireDate = [[timer fireDate] retain];
[timer setFireDate:[NSDate distantFuture]];
}
-(void) resumeTimer:(NSTimer *)timer {
float pauseTime = -1*[pauseStart timeIntervalSinceNow];
[timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];
[pauseStart release];
[previousFireDate release];
}
I was also in need of pausable NSTimer. After reading this thread and your answers and realizing that only thing I need is to set timer's fireDate to distant future and then back to Now I made category to NSTimer with Pause and Resume methods. So I can simply pause timer by calling [myTimer pause]; and resume by calling [myTimer resume]; methods. Here it is, hope that it will help someone.
Interface:
#import <Foundation/Foundation.h>
@interface NSTimer (Pausable)
-(void)pause;
-(void)resume;
@end
Implementation:
#import "NSTimer+Pausable.h"
@implementation NSTimer (Pausable)
-(void)pause
{
[self setFireDate:[NSDate dateWithTimeIntervalSinceNow:[NSDate distantFuture]]]; //set fireDate to far future
}
-(void)resume
{
[self setFireDate:[NSDate date]];
}
@end
Feel free to reuse my category on NSTimer
:
https://github.com/keeshux/ios-components/tree/master/Components/Categories/NSTimer%2BPause