iOS app Stopwatch and Timer stop counting when in background - ios

I have an iPhone app which is basically a clock, stopwatch, and timer. Everything works fine, until you leave the app, and the stopwatch and timer stop counting. I think the way to solve this would be to start a counter when the app is put into the background, and add/subtract that from the stopwatch/timer when the app starts up again. Is this the best thing to do, and if so, how would I do this?
UPDATE: The timer, stopwatch, and clock are all in different view controllers. Here is the code for my stopwatch in StopwatchViewController.m:
- (void)stopwatch // Start counting the stopwatch
{
hourInt = [hourLabel.text intValue]; // Store integer values of time
minuteInt = [minuteLabel.text intValue];
secondInt = [secondLabel.text intValue];
if (secondInt == 59) { // Add on one second
secondInt = 0;
if (minuteInt == 59) {
minuteInt = 0;
if (hourInt == 23) {
hourInt = 0;
} else {
hourInt += 1;
}
} else {
minuteInt += 1;
}
} else {
secondInt += 1;
}
NSString *hourString = [NSString stringWithFormat:#"%02d", hourInt]; // Update text to show time
NSString *minuteString = [NSString stringWithFormat:#"%02d", minuteInt];
NSString *secondString = [NSString stringWithFormat:#"%02d", secondInt];
hourLabel.text = hourString;
minuteLabel.text = minuteString;
secondLabel.text = secondString;
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:NO]; // Repeat every second
}
How do I get it to add on the elapsed time the app was in the background?

Add two observers for when app enter in background and for app enterForeGround
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterInBackGround) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterInForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
- (void)applicationWillEnterInBackGround{
// stop your timer here
}
- (void)applicationWillEnterInForeground
{
// start your timer here
}
Example ------
in .h
NSTimer *timer;
float time;
in .m
- (void)viewDidLoad
{
[super viewDidLoad];
timer=[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterInBackGround) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterInForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)applicationWillEnterInBackGround
{
[timer invalidate];
}
- (void)applicationWillEnterInForeground
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:YES];
}
- (void)stopwatch // Start counting the stopwatch
{
hourInt = [hourLabel.text intValue]; // Store integer values of time
minuteInt = [minuteLabel.text intValue];
secondInt = [secondLabel.text intValue];
if (secondInt == 59) { // Add on one second
secondInt = 0;
if (minuteInt == 59) {
minuteInt = 0;
if (hourInt == 23) {
hourInt = 0;
} else {
hourInt += 1;
}
} else {
minuteInt += 1;
}
} else {
secondInt += 1;
}
NSString *hourString = [NSString stringWithFormat:#"%02d", hourInt]; // Update text to show time
NSString *minuteString = [NSString stringWithFormat:#"%02d", minuteInt];
NSString *secondString = [NSString stringWithFormat:#"%02d", secondInt];
hourLabel.text = hourString;
minuteLabel.text = minuteString;
secondLabel.text = secondString;
}

The easiest way to proceed and save the current time when the application goes to background and then when come back to foreground compute the time interval and add it to your counters, something like:
.. going background ..
NSDate *backgroundTime = [[NSDate date] retain];
.. returning foreground ..
NSTimeInterval elapsed = [NSDate timeIntervalSinceDate:date];
[date release];
yourTimer -= elapsed;
To understand when exactly you should hook these events you should take a look here.

Related

Keep label updating through segue

I have a small sample project i'm using to figure out how to implement this on my main project. This simple project has 2 VC's both with segues to each other.
On the initial VC is a button which leads to the TimerVC (both using the same class).
The TimerVC has a button and a label. When the button is pressed the label will increase by 1 every second.
If the timer is on and I segue back to the initial VC and then to the TimerVC the timer continues but the label stops updating.
How can I keep the label updating? The timer keeps going in the back-end but once the segue happens the label stops updating.
EDIT: Code provided below. The Timer is also more complex to represent a little of what I'm trying to do.
VC.H
NSTimer * timer;
NSTimer * updateTimer;
#property (weak, nonatomic) IBOutlet UIButton *idleOutler;
- (IBAction)idleAttack:(id)sender;
VC.M
int enemy001Hp = 100;
int deadEnemy = NO;
int noHealth = 0;
bool enemy001Active = NO;
- (void) enemy1 {
enemy001Active = YES;
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy001Hp];
}
- (void) enemyDamageTimer {
if (enemy001Active == YES) {
enemy001Hp -= 50;
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i",enemy001Hp];
}
}
- (void) updateLabelTimer {
if (enemy001Active == YES) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy001Hp];
}
}
- (void) stopTimer {
[timer invalidate];
timer = nil;
self.enemyHpLabel.text = [NSString stringWithFormat:#"0"];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self enemy1];
if (enemy001Active) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy001Hp];
} else if (enemy002Active == YES) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy002Hp];
}
}
- (IBAction)idleAttack:(id)sender {
idleOn = YES;
self.idleOutler.hidden = YES;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(enemyDamageTimer) userInfo:nil repeats:YES];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(updateLabelTimer) userInfo:nil repeats:YES];
if (enemy001Active == YES) {
if (enemy001Hp <= 0) {
[self enemy2];
enemy001Active = NO;
enemy002Active = YES;
}
} else if (enemy002Active == YES) {
if (enemy002Hp <= 0) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"0"];
enemy002Hp = 0;
enemy002Active = NO;
[self stopTimer];
}
}
}

Cant get my NSTimer to end when the value <= 0

i cant seem to get my NSTimer to stop when it reaches 0 i've looked around for an answer and i tryed everything please help.
-(void)count {
mainint -= 1;
seconds.text = [NSString stringWithFormat:#"%i", mainint];
}
-(IBAction)start:(id)sender {
play.hidden = YES;
mainint = 60;
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(count) userInfo:nil repeats:YES];
if (timer1 == 0) {
[timer1 invalidate];
timer1 = nil;
}
}
put your if condition in count() function
-(void)count {
mainint -= 1;
seconds.text = [NSString stringWithFormat:#"%i", mainint];
if (mainint == 0) {
[timer1 invalidate];
}
}
The issue is that your timer will never be nil (0) unless you set it yourself to nil. I think what you're trying to achieve is something like this :
-(void)count {
mainint -= 1;
seconds.text = [NSString stringWithFormat:#"%i", mainint];
if (mainint <= 0) {
[timer1 invalidate];
timer1 = nil;
}
}

Counting time spent in background for an app using NSNotificationCenter

I am making a stopwatch, but it stops counting when the app is put into the background. I have tried to count the time that the app spends in the background, and then use NSNotificationCenter to send that time in seconds to my StopwatchViewController where I can add on the elapsed time. However, it does not seem to work:
In my AppDelegate.m file:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSDate *currentDate= [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:#"backgroundDate"];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSDate *dateWhenAppGoesBg= (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:#"backgroundDate"];
NSTimeInterval timeSpentInBackground = [[NSDate date] timeIntervalSinceDate:dateWhenAppGoesBg];
NSNumber *n = [NSNumber numberWithDouble:timeSpentInBackground];
[[NSNotificationCenter defaultCenter] postNotificationName:#"NEWMESSAGE" object:n];
NSLog(#"%d", [n integerValue]);
}
In my StopwatchViewController.m file:
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle { // Initialise view controller
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(newMessageReceived:) name:#"NEWMESSAGE" object:nil];
return self;
}
-(void)newMessageReceived:(NSNotification *) notification{
elapsedTime = [[notification object] intValue];
elapsedHours = elapsedTime / 3600;
elapsedTime = elapsedTime - (elapsedTime % 3600);
elapsedMinutes = elapsedTime / 60;
elapsedTime = elapsedTime - (elapsedTime % 60);
elapsedSeconds = elapsedTime;
secondInt = secondInt + elapsedSeconds;
if (secondInt > 59) {
++minuteInt;
secondInt -= 60;
}
minuteInt = minuteInt + elapsedMinutes;
if (minuteInt > 59) {
++hourInt;
minuteInt -= 60;
}
hourInt = hourInt + elapsedHours;
if (hourInt > 23) {
hourInt = 0;
}
}
If I am not completely missing the point, I think you are attacking the problem in the wrong way.
If you are creating a stopwatch, the only two interesting points in time are the point when you started the stopwatch and the current time. There is no reason to calculate the time that passed when your app was in the background.
Instead, just store the point in time where your stopwatch was started, then add e.g. a NSTimer that updates the timer display by comparing this time with the current time (i.e. [NSDate date). Then you won't have to worry about what happens when your app enter background mode.
EDIT Some ideas (disclaimer: did not have access to Xcode, so I just typed this up from my head):
When the user starts the timer, save the current time and start a NSTimer
- (void) didTapStart:(id)sender {
self->startTime = [NSDate date];
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(timerElapsed:) userInfo:nil repeats:YES];
}
Then update the display on the timer events
- (void) timerElapsed:(id)sender {
NSDateInterval elapsed = [[NSDate date] timeIntervalSinceDate:self->startTime];
int hours = (int)elapsed / 3600;
int minutes = ((int)elapsed / 60) % 60;
int seconds = (int)elapsed % 60;
NSString* elapsedString = [NSString stringWithFormat:#"Elapsed: %d:%02d:%02d",hours,minutes,seconds];
}

NSTimer Milliseconds countdown

I would like to make a simple countdown with seconds and millisecond: SS:MM.
However, i would like to stop the timer or do something when the timer reach 0:00.
Currently the timer works, but it doesnt stop at 0:00. I can make the seconds stop, but not the milliseconds. What is wrong?
-(void) setTimer {
MySingletonCenter *tmp = [MySingletonCenter sharedSingleton];
tmp.milisecondsCount = 99;
tmp.secondsCount = 2;
tmp.countdownTimerGame = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
-(void) timerRun {
MySingletonCenter *tmp = [MySingletonCenter sharedSingleton];
tmp.milisecondsCount = tmp.milisecondsCount - 1;
if(tmp.milisecondsCount == 0){
tmp.secondsCount -= 1;
if (tmp.secondsCount == 0){
//Stuff for when the timer reaches 0
//Also, are you sure you want to do [self setTimer] again
//before checking if there are any lives left?
[tmp.countdownTimerGame invalidate];
tmp.countdownTimerGame = nil;
tmp.lives = tmp.lives - 1;
NSString *newLivesOutput = [NSString stringWithFormat:#"%d", tmp.lives];
livesLabel.text = newLivesOutput;
if (tmp.lives == 0) {
[self performSelector:#selector(stopped) withObject:nil];
}
else {[self setTimer]; }
}
else
tmp.milisecondsCount = 99;
}
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%2d", tmp.secondsCount, tmp.milisecondsCount];
timeLabel.text = timerOutput;
}
-(void) stopped {
NSLog(#"Stopped game");
timeLabel.text = #"0:00";
}
Well. You do
tmp.milisecondsCount = tmp.milisecondsCount - 1;
if(tmp.milisecondsCount == 0){
tmp.milisecondsCount = 100;
tmp.secondsCount -= 1;
}
And right after that
if ((tmp.secondsCount == 0) && tmp.milisecondsCount == 0) {
//stuff
}
How could it ever happen that they're both 0 if, as soon as milisecond reaches 0, you reset it to 100?
EDIT: Do instead something like:
if(tmp.milisecondsCount < 0){
tmp.secondsCount -= 1;
if (tmp.secondsCount == 0){
//Stuff for when the timer reaches 0
//Also, are you sure you want to do [self setTimer] again
//before checking if there are any lives left?
}
else
tmp.milisecondsCount = 99;
}
In your code, a first condition is met
if(tmp.milisecondsCount == 0){
tmp.milisecondsCount = 100;
so that the next conditional statment
&& tmp.milisecondsCount == 0
will never be true.

Stopwatch counting in powers of 2

I am making a stopwatch in Objective-C:
- (void)stopwatch
{
NSInteger hourInt = [hourLabel.text intValue];
NSInteger minuteInt = [minuteLabel.text intValue];
NSInteger secondInt = [secondLabel.text intValue];
if (secondInt == 59) {
secondInt = 0;
if (minuteInt == 59) {
minuteInt = 0;
if (hourInt == 23) {
hourInt = 0;
} else {
hourInt += 1;
}
} else {
minuteInt += 1;
}
} else {
secondInt += 1;
}
NSString *hourString = [NSString stringWithFormat:#"%d", hourInt];
NSString *minuteString = [NSString stringWithFormat:#"%d", minuteInt];
NSString *secondString = [NSString stringWithFormat:#"%d", secondInt];
hourLabel.text = hourString;
minuteLabel.text = minuteString;
secondLabel.text = secondString;
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:YES];
}
The stopwatch has three separate labels, if you were wondering, for hours, minutes and seconds. However, instead on counting by 1, it counts like 2, 4, 8, 16, etc.
Also, another issue with the code (quite a minor one) is that it doesn't display all numbers as two digits. For example it's shows the time as 0:0:1, not 00:00:01.
Any help is really appreciated! I should add that I am really new to Objective-C so keep it as simple as possible, thank you!!
Don't use repeats:YES if you schedule the timer at every iteration.
You're spawning one timer at every iteration and the timer is already repeating, resulting in an exponential growth of timers (and consequently of method calls to stopwatch).
Change the timer instantiation to:
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(stopwatch)
userInfo:nil
repeats:NO];
or start it outside the stopwatch method
For the second issue simply use a proper format string.
NSString *hourString = [NSString stringWithFormat:#"%02d", hourInt];
NSString *minuteString = [NSString stringWithFormat:#"%02d", minuteInt];
NSString *secondString = [NSString stringWithFormat:#"%02d", secondInt];
%02d will print a decimal number padding it with 0s up to length 2, which is precisely what you want.
(source)
For the first issue, instead of creating a timer instance for every call.Remove the line
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:YES];
from the function stopwatch.
Replace your call to function stopwatch with the line above. i.e replace
[self stopwatch]
with
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:YES];

Resources