how to pause and resume NSTimer in iphone - iphone

hello
I am developing small gameApp.
I need to pause the timer,when user goes to another view [say settings view].
when user comes back to that view , I need to resume the timer.
can anybody solve this issue ...
Thanks in Advance...

NSTimer does not give you the ability to pause it. However, with a few simple variables, you can create the effect yourself:
NSTimer *timer;
double timerInterval = 10.0;
double timerElapsed = 0.0;
NSDate *timerStarted;
-(void) startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:(timerInterval - timerElapsed) target:self selector:#selector(fired) userInfo:nil repeats:NO];
timerStarted = [NSDate date];
}
-(void) fired {
[timer invalidate];
timer = nil;
timerElapsed = 0.0;
[self startTimer];
// react to timer event here
}
-(void) pauseTimer {
[timer invalidate];
timer = nil;
timerElapsed = [[NSDate date] timeIntervalSinceDate:timerStarted];
}
This has been working out quite well for me.

You can't pause a timer. However, when the user goes to the settings view, you can save the fireDate of the timer and also the current date. After this you invalidate the timer and let the user do his/her stuff.
Once he/she switches back to the game, you create a new timer object and set the fire date to the old fire date plus the time the user was in the menu (oldTime + currentTime).

you can use this code to implement pause and resume functionality in NSTimer
//=========new timer update method=============
-(void) updateTimer {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [dateFormatter stringFromDate:timerDate];
lblMessage.text = timeString;
pauseTimeInterval = timeInterval;
}
-(IBAction) startBtnPressed:(id)sender
{
//=============================new update with start pause==================
if(running == NO) {
running = YES;
startDate = [NSDate date] ;
startDate = [[startDate dateByAddingTimeInterval:((-1)*(pauseTimeInterval))] retain];
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
}
else {
running = NO;
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self updateTimer];
}
}
declare in .h file NSDate startDate;
NSTimeInterval pauseTimeinterval;
and in viewdidload pausetimeInterval=0.0; good luck

You can't pause an NSTimer. You can, however invalidate it and create a new one when needed.

on Start -
startNewCapture = [NSDate date];
on Pause -
captureSessionPauseDate = [NSDate date];
captureSessionTimeInterval = [captureSessionPauseDate timeIntervalSinceDate:startNewCapture];
on Resume -
NSDate *dateNow = [NSDate date];
startNewCapture = [NSDate dateWithTimeInterval:-captureSessionTimeInterval sinceDate:dateNow];

- (IBAction)pauseResumeTimer:(id)sender {
if (timerRunning == NO) {
timerRunning = YES;
[pauseTimerBtn setTitle:#"Resume" forState:UIControlStateNormal];
NSString *stringVal = [NSString stringWithFormat:#"%#",timeTxt.text];
stringVal = [stringVal stringByReplacingOccurrencesOfString:#":" withString:#"."];
float tempFloatVal = [stringVal floatValue];
int minuteValue = floorf(tempFloatVal);
float tempSecVal = [stringVal floatValue] - floorf(tempFloatVal);
int secondVal = tempSecVal*100;
minuteValue = minuteValue*60;
oldTimeValue = minuteValue + secondVal;
[timer invalidate];
timer = nil;
}
else
{
timerRunning = NO;
[pauseTimerBtn setTitle:#"Pause" forState:UIControlStateNormal];
startDate = [NSDate date];
timer = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(timer:) userInfo:nil repeats:YES];
}
}
- (void)runTimer:(NSTimer *)timer {
NSInteger secondsAtStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:startDate];
secondsAtStart = secondsAtStart + oldTimeValue;
NSInteger seconds = secondsAtStart % 60;
NSInteger minutes = (secondsAtStart / 60) % 60;
NSInteger hours = secondsAtStart / (60 * 60);
NSString *result = nil;
result = [NSString stringWithFormat:#"%02ld:%02ld",(long)minutes,(long)seconds];
timeTxt.text = result;
}

Did you end up figuring it out? I saw that you said that you cannot invalidate your timer when you go into another view. Can you explain what you mean by that? NSTimers cannot be paused, and methods to simulate pausing them usually involve invalidating them. You can then simulate "unpausing" by creating a new timer that will then start up again. This will simulate a timer being paused and unpaused.
For people who would like a potentially more convenient method, I wrote a controller class that can conveniently handle pausing and unpausing timers. You can find it here: https://github.com/LianaChu/LCPausableTimer
You can use the controller class that I wrote to create new timers, and then you can pause and unpause the timers by call the methods "pauseTimer" and "unpauseTimer" on the controller class.
I would greatly appreciate any comments or feedback like how you used it, what changes you would like to see, or any features you would like me to add. Please don't hesitate to reply with your comments here, or post on the issues tab on the Github page.

Related

Saving NSTimer's time to disk on entering background and retrieving on foreground

I want my timer to work in the background, and I have figured the only way to actually do that is to save the time on appliationDidEnterBackground and retrieve it with applicationDidLaunchWithOptions. My timer uses Core Data and my timeInterval (testTask.timeInterval) is saved after every decrement as so:
-(IBAction)startTimer:(id)sender{
if (timer == nil) {
[startButton setTitle:#"Pause" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
} else {
[startButton setTitle:#"Resume" forState:UIControlStateNormal];
[timer invalidate];
timer = nil;
}
}
-(void)timerAction:(NSTimer *)t
{
if(testTask.timeInterval == 0)
{
if (self.timer)
{
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
}
else
{
testTask.timeInterval--;
NSError *error;
if (![self.context save:&error]) {
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
}
NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
NSString *string = [NSString stringWithFormat:#"%02u:%02u:%02u",
seconds / 3600, (seconds / 60) % 60, seconds % 60];
timerLabel.text = string;
NSLog(#"%f", testTask.timeInterval);
}
-(void)timerExpired{
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = #"Time is up";
localNotification.alertAction = #"Ok";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication]presentLocalNotificationNow:localNotification];
}
These methods are in a detail view controller, which is initialized by this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
}
What I don't understand is...how would I access the timeInterval (each detailviewcontroller has a different timeinterval...) such that I can put it in appliationDidEnterBackground? Also, I am guessing I should save the time that the application enters the background and then save the time when it enters the foreground, and subtract? and then I would subtract that value from the timeinterval, correct?
First, you shouldn't be using a direct countdown like this if you care at all about accuracy. The NSTimer will not fire on exact one-second intervals, and delays will accumulate. The better way to do this is to create an NSDate when the timer starts, and get the NSTimeInterval since then at each tick, then calculate the minutes and seconds.
For each view controller to store its own start time, you can register any object to receive the UIApplicationDidEnterBackgroundNotification. This is posted at the same time that the app delegate gets applicationDidEnterBackground:.
Also, I am guessing I should save the time that the application enters the background and then save the time when it enters the foreground, and subtract? and then I would subtract that value from the timeinterval, correct?
Yes:
NSTimeInterval idleTime = [dateReturnedToForeground timeIntervalSinceDate:dateEnteredBackground];
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
elapsedTime -= idleTime;
will give you the active time that has elapsed for your timer.

NSTimer crashes with bad access

I have the following method to update a label which show a simple time up
-(void)updateTimeLabel:(NSTimer *)timer{
NSInteger secondsSinceStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:_startTime];
NSInteger seconds = secondsSinceStart % 60;
NSInteger minutes = (secondsSinceStart / 60) % 60;
NSInteger hours = secondsSinceStart / (60 * 60);
NSString *result = nil;
if (hours > 0) {
result = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
}
else {
result = [NSString stringWithFormat:#"%02d:%02d", minutes, seconds];
}
_totalTime = result;
_totalTimeLabel.text = result;
}
I then call this as the action to a button:
-(IBAction) startTimer{
_startTime = [NSDate date];
_walkRouteTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimeLabel:) userInfo:nil repeats:YES];
[_walkRouteTimer fire];
}
But when I run the app I get a bad access error and the app crashes, can anyone help me with this?
Thanks in advance
Are you using ARC? If not, _startTime = [NSDate date]; this line will cause your problem. [NSDate date] returned an autorelease object and _startTime will not hold it if you are not using ARC(or using ARC but declared _startTime as weak).
If so, try to add a retain to it
-(IBAction) startTimer{
//_startTime = [NSDate date]
_startTime = [[NSDate date] retain];
_walkRouteTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimeLabel:) userInfo:nil repeats:YES];
[_walkRouteTimer fire];
}
And when you finished your timer, after calling of [_walkRouteTimer invalidate], call [_startTime release].
Or even simpler, if you use property for startTime and declared it as retain. Just use dot notation:
-(IBAction) startTimer{
//_startTime = [NSDate date]
self.startTime = [NSDate date];
_walkRouteTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimeLabel:) userInfo:nil repeats:YES];
[_walkRouteTimer fire];
}
...
//After [_walkRouteTimer invalidate]
self.startTime = nil;
Try adding an exception breakpoint to see which line is crashing:
1) Click on the breakpoint tab (second from the right)... kinda looks like a right pointing arrow or "next" button
2) Click on the "+" in the bottom left corner of the tab menu
3) Select "Add Exception Breakpoint"
4) (Optional) Select "Exception" drop down and change to "Objective-C"
5) Select "Done"
6) Run your code again and try to generate the crash... when you do, it will hopefully be caught by this breakpoint, and you'll see which line is crashing and be able to fix it
Good luck

Implementing a Countdown Timer in Objective-c?

I am new to iOS programming. I am working on words matching game. In this game I need to implement time counter which shows minutes and seconds. I want when my game is started my timer to start with 3 minutes. Now I want to decrease this timer in reverse direction with seconds. my code just work for seconds..here is my code:
secondsLeft--;
int seconds = (secondsLeft %3600) % 60;
Timerlbl.text = [NSString stringWithFormat:#"%02d",seconds];
if (seconds==0)
{
UIAlertView *pAlert = [[UIAlertView alloc]initWithTitle:#"Sorry!!"
message:#"TimeOver" delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:#"Cancel",nil];
[pAlert show];
[pAlert release];
}
}
In Viewdidload i call it through timer..
countDown=[NSTimer scheduledTimerWithTimeInterval:5.0 target:self
selector:#selector(TimeOver) userInfo:nil repeats:YES];
pleas any one guide me how can i do it in both minutes and seconds.
You can do it like this(ARC Enabled):-
#interface ViewController()
{
UILabel *progress;
NSTimer *timer;
int currMinute;
int currSeconds;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
progress=[[UILabel alloc] initWithFrame:CGRectMake(80, 15, 100, 50)];
progress.textColor=[UIColor redColor];
[progress setText:#"Time : 3:00"];
progress.backgroundColor=[UIColor clearColor];
[self.view addSubview:progress];
currMinute=3;
currSeconds=00;
// Do any additional setup after loading the view, typically from a nib.
}
-(void)start
{
timer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:nil repeats:YES];
}
-(void)timerFired
{
if((currMinute>0 || currSeconds>=0) && currMinute>=0)
{
if(currSeconds==0)
{
currMinute-=1;
currSeconds=59;
}
else if(currSeconds>0)
{
currSeconds-=1;
}
if(currMinute>-1)
[progress setText:[NSString stringWithFormat:#"%#%d%#%02d",#"Time : ",currMinute,#":",currSeconds]];
}
else
{
[timer invalidate];
}
}
I know this is old question, but I want to share my way to achieve this. We have method (timeIntervalSinceDate:) to calculate the interval and have fixed amount of seconds to count down. In my case 300 s.
UPDATE JULY, 2015 SWIFT
#IBAction func startTimer(sender: UIButton) {
sender.selected = !sender.selected;
//if selected fire timer, otherwise stop
if (sender.selected) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("updateTimer"), userInfo: nil, repeats: true);
self.startDate = NSDate();
} else {
self.stopTimer();
}
}
func stopTimer() {
self.timer.invalidate();
}
func updateTimer() {
// Create date from the elapsed time
var currentDate:NSDate = NSDate();
var timeInterval:NSTimeInterval = currentDate.timeIntervalSinceDate(self.startDate!);
//300 seconds count down
var timeIntervalCountDown = 300 - timeInterval;
var timerDate:NSDate = NSDate(timeIntervalSince1970: timeIntervalCountDown);
// Create a date formatter
var dateFormatter = NSDateFormatter();
dateFormatter.dateFormat = "mm:ss";
dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0);
// Format the elapsed time and set it to the label
var timeString = dateFormatter.stringFromDate(timerDate);
self.timerLabel?.text = timeString;
}
Working github swift sample project
Objective-C
- (void)updateTimer
{
// Create date from the elapsed time
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate
timeIntervalSinceDate:self.startDate];
NSLog(#"time interval %f",timeInterval);
//300 seconds count down
NSTimeInterval timeIntervalCountDown = 300 - timeInterval;
NSDate *timerDate = [NSDate
dateWithTimeIntervalSince1970:timeIntervalCountDown];
// Create a date formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
// Format the elapsed time and set it to the label
NSString *timeString = [dateFormatter stringFromDate:timerDate];
self.stopWatch.text = timeString;
}
- (void)stopTimer
{
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
[self updateTimer];
}
- (void)startTimer
{
if (self.stopWatchTimer) {
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
}
self.startDate = [NSDate date];
// Create the stop watch timer that fires every 100 ms
self.stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
UPDATE:
it is extremely expensive to create a NSDateFormatter each time you pass through updateTimer. Far better to create the NSDateFormatter outside the loop and reuse it. Credits: #siburb
In .h file
IBOutlet UILabel* lblTimer;
NSTimer* gameTimer;
In .m file
-(void)viewDidLoad
{
[super viewDidLoad];
[self createTimer];
}
- (void)createTimer
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
gameTimer = [[NSTimer timerWithTimeInterval:1.00 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
timeCount = 20;
[runLoop run];
[pool release];
}
- (void)timerFired:(NSTimer *)timer
{
if(timeCount == 0)
{
[self timerExpired];
}
else
{
timeCount--;
if(timeCount == 0) {
[timer invalidate];
[self timerExpired];
}
}
lblTimer.text = [NSString stringWithFormat:#"%02d:%02d",timeCount/60, timeCount % 60];
self.title = lblTimer.text;
}
Bind the above Label Outlet with Label in View.
It is working correctly with Me
Simple and less code in Objective-C,
in .h file create objects for Timer and totalSeconds
NSTimer *timer;
int totalSeconds;
.m file
//you can call this method where you want
[self startTimer];
-(void)startOtpTimer {
totalSeconds = 120;//your time
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:#selector(updateCountDownTime) userInfo:nil repeats:YES];
}
-(void)updateCountDownTime {
[self populateLabelwithTime:totalSeconds];
if( totalSeconds != 0) {
totalSeconds -= 1;
} else {
[timer invalidate];//stop timer
self.timeLabel.text = #"";//after reach 0 you can set empty string
}
}
- (void)populateLabelwithTime:(int)seconds {
self.timeLabel.text = [NSString stringWithFormat:#"%02d", totalSeconds];
}
You can use NSTimer to achieve this.

Updating NSDateFormatter with NSTimer

I have created a custom class which show time date formatter and I need a timer like method to update the seconds, so here is my code :
CustomClass.m
- (NSString *) showLocaleTime {
NSDateFormatter *timeFormater = [[NSDateFormatter alloc] init];
timeFormater = [setDateFormat:#"HH:mm:ss "];
NSString *currDay = [timeFormater stringFromDate:[NSDate date]];
currDay = [NSString stringWithFormat:#"%#",currDay];
[timeFormater release];
return timer;
}
- (void) updateLocaleTime {
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(showLocaleTime) userInfo:nil repeats:YES];
}
viewController.m :
CustomClass *time = [[CustomClass alloc]init];
label.text = [time showLocaleTime];
[time updateLocaleTime];
But the problem is the updateLocaleTime does not call to update seconds ! am I missing something ?
Thanks
Instead of calling updateLocaleTime in CustomClass, just start the timer in the view controller itself.
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(updateLocaleTime) userInfo:nil repeats:YES];
Add updateLocaleTime method to the viewController
- (void) updateLocaleTime {
CustomClass *time = [[CustomClass alloc]init];
label.text = [time showLocaleTime];
[time release];
}
But here we are allocating and releasing the CustomClass again and again for every 0.5 seconds. Instead you declare it as class member, in .h file and allocate that in viewDidLoad.
So no need to allocate in updateLocaleTime method. Also release that time in viewDidUnload method.
Where do you update the label text, after computing the new time? (You compute then new time but the it falls on the floor.)
Eg, add label.text = timer; to your showLocalTime method, and skip returning timer.

NSTimer Lap Time iPhone

This is my code for my stopwatch. It all works except for the lap button function. How would i be able to implement a lap time where when the ib action "lap" is pressed it would store the current time in an array and list the lap times on the view?
I have already tried to create a database but this seemed far to complex for something of this nature.
#import "FirstViewController.h"
#implementation FirstViewController
#synthesize start;
#synthesize stop;
#synthesize lap;
#synthesize reset;
#synthesize lapLabel;
#synthesize stopWatchLabel;
NSDate *startDate;
NSDateFormatter *dateFormatter;
NSTimer *stopWatchTimer;
NSTimeInterval secondsAlreadyRun;
int touchCount;
-(void)showActivity:(NSTimer *)tim {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
// Add the saved interval
timeInterval += secondsAlreadyRun;
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
}
- (IBAction)onStartPressed:(UIButton *)sender {
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
target:self
selector:#selector(showActivity:)
userInfo:nil
repeats:YES];
// Save the new start date every time
startDate = [[NSDate alloc] init]; // equivalent to [[NSDate date] retain];
[stopWatchTimer fire];
touchCount +=1;
if (touchCount == 1)
{
start.hidden = YES;
stop.hidden = NO;
reset.hidden = YES;
lap.hidden = NO;
touchCount = 0;
}
}
- (IBAction)onStopPressed:(UIButton *)sender {
// _Increment_ secondsAlreadyRun to allow for multiple pauses and restarts
secondsAlreadyRun += fabs([startDate timeIntervalSinceNow]);
[stopWatchTimer invalidate];
stopWatchTimer = nil;
stop.hidden = YES;
start.hidden = NO;
reset.hidden = NO;
lap.hidden = YES;
}
- (IBAction)reset:(UIButton *)sender; {
secondsAlreadyRun = 0;
stopWatchLabel.text = #"00:00.00";
}
- (IBAction)lap:(UIButton *)sender; {
//Lap Code will go here.
}
- (void)viewDidUnload {
[self setStart:nil];
[self setStop:nil];
[self setLap:nil];
[self setReset:nil];
[self setLap:nil];
[super viewDidUnload];
}
#end
Just use a NSMutableArray stored in the class. Every time the person clicks the lap-button
For instance call
NSMutableArray *lapTimes;
And in your method do:
- (IBAction)lap:(UIButton *)sender {
double timeSinceStart = [startDate timeIntervalSinceNow];
[lapTimes addObject:[NSNumber numberWithDouble:-timeSinceStart]];
}
timeIntervalSinceNow will return the difference in time between the NSDate object and now in seconds. If the NSDate is earlier then now (like in your case), the returned number will be negative, so you will have to invert it.