Counting time spent in background for an app using NSNotificationCenter - ios

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];
}

Related

NSTimer countdown not functioning correctly

I am trying to do the difference between time and display the countdown timer to the user in a ui label
declaration of NSTimer
#property (strong, nonatomic)NSTimer *timer;
this is my timer in view did load
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(updateCountdown:) userInfo:nil repeats: YES];
and this is my updateCountdown method
-(void) updateCountdown:(int)secondsLeft {
int hours, minutes, seconds;
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(#"%#",[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
and my log details are
2017-06-30 09:49:34.070 Barebones[845:56963] requestReply cust liqour category: {
date = "30-6-2017";
"end_time" = "11:41";
"market_crash" = true;
"start_time" = "09:41";
}
2017-06-30 09:49:34.070 Barebones[845:56963] true
2017-06-30 09:49:34.070 Barebones[845:56963] 09:41
2017-06-30 09:49:34.070 Barebones[845:56963] 11:41
2017-06-30 09:49:34.070 Barebones[845:56963] 30-6-2017
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 08:58:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 12:15:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 197.000000 is the time difference
2017-06-30 09:49:34.073 Barebones[845:56963] 00:03:17
2017-06-30 09:49:35.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:36.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:37.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:38.075 Barebones[845:56963] 991:05:35
and this value goes on executing
Aim:- to countdown till zero and stop the timer actually I ll be hiding the label once the countdown is over
Update:-
int secondsLeft=[self timeFormatted:[date2 timeIntervalSinceDate:date1]/60];
have initialised this above timer
this is my updated timer:-
int secondsLeft=[date2 timeIntervalSinceDate:date1]/60;
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:secondsLeft], #"cID", nil];
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(updateCountdown:) userInfo:userInfo repeats: YES];
and this is my updated timer method :-
- (void)updateCountdown:(NSTimer *)timer{
int hours, minutes, seconds;
NSDictionary *userInfo = [timer userInfo];
int secondsLeft = [[userInfo objectForKey:#"cID"] intValue];
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(#"%#",[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
A couple of things.
As vadian said, if you use the selector-based timer, the timer handler function takes a single parameter which is a reference to the timer itself. If you want to keep track of the count down, you can have define properties to keep track of that:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UILabel *label;
#property (nonatomic, weak) NSTimer *timer;
#property (nonatomic, strong) NSDateComponentsFormatter *formatter;
#property (nonatomic, strong) NSDate *stopTime;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.formatter = [[NSDateComponentsFormatter alloc] init];
self.formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
self.formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
self.formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
self.stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(handleTimer:) userInfo:nil repeats:true];
[self.timer fire]; // don't wait one second before firing the first time; fire now
}
- (void)handleTimer:(NSTimer *)timer {
NSDate *now = [NSDate date];
if ([now compare:self.stopTime] == NSOrderedDescending) {
// do whatever you want when timer stops
[timer invalidate];
return;
}
self.label.text = [self.formatter stringFromDate:now toDate:self.stopTime];
}
// Note, when the view disappears, invalidate the timer so the timer doesn't
// keep strong reference to the view controller. Note that in this selector-based
// pattern, I can't attempt to do this in `dealloc`, because the scheduled timer
// will keep a strong reference, preventing `dealloc` from getting called. So do
// this in `viewDidDisappear`.
- (void)viewDidDisappear:(BOOL)animated {
[self.timer invalidate];
}
#end
If only supporting iOS 10 and later, I'd suggest using the completion block timer, as it simplifies the process even further:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UILabel *label;
#property (nonatomic, weak) NSTimer *timer;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
NSDate *stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example
typeof(self) __weak weakSelf = self; // make sure to not reference `self` in block below, but only reference `weakSelf` to avoid timer from maintaining strong reference
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) {
NSDate *now = [NSDate date];
if ([now compare:stopTime] == NSOrderedDescending) {
// do whatever you want when timer stops
[timer invalidate];
return;
}
weakSelf.label.text = [formatter stringFromDate:now toDate:stopTime];
}];
[self.timer fire]; // don't wait one second before firing the first time; fire now
}
// Because I was careful to not reference `self` inside the above block,
// this block-based timer will NOT keep a strong reference to the view
// controller. Nonetheless, when the view controller is dismissed, I want
// to stop the timer, to avoid wasting CPU cycles on a timer that isn't
// needed anymore.
- (void)dealloc {
[self.timer invalidate];
}
#end
Clearly, if you need to support iOS 9 and earlier, too, then you have to use the aforementioned selector-based solution.
I'd suggest not relying on the timer to adjust the time, as you're not assured that the timer will be called with the frequency you want. In both of the above examples, I capture to what time I'm counting down, and just display the amount of time between "now" and that scheduled "stop time".
Note, I'm also suggesting that you get out of the business of building the format string yourself. There's a convenient NSDateComponentsFormatter which can format this for you. Use it, if you can.
You made your timer reference strong. You can make it weak because a scheduled timer is not deallocated until it's invalidated. And once it's invalidated, it's convenient to have it be deallocated automatically for you.
Your action method cannot work.
If a parameter is passed it must be a NSTimer instance
- (void)updateCountdown:(NSTimer *)timer
To pass custom parameters use the userInfo argument.
Nevertheless a more suitable solution is to use an instance variable or property because the value of userInfo must be an object like NSNumber
Create property
#property (nonatomic) int secondsLeft;
in viewDidLoad
self.secondsLeft = 5 * 60; // 5 minutes
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(updateCountdown) userInfo:nil repeats: YES];
Selector:
-(void)updateCountdown{
int secondsLeft = self.secondsLeft;
if (secondsLeft >= 0) {
int minutes, seconds;
int hours;
self.secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
NSString *time = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
_countDownlabel.text = [self timeFormatted:secondsLeft];
}
if (secondsLeft <= 0) {
NSLog(#"TIME ENDS");
if ([self.timer isValid]) {
[self.timer invalidate];
self.timer = nil;
}
}
}

Calculate the Seconds duration between app did enter background to app did enter foreground

I came to know NSTimer wont work in background. My app require me to run timer even in background. What i have decide to do is calculate the seconds duration from App
- (void)applicationDidEnterBackground:(UIApplication *)application
To
- (void)applicationWillEnterForeground:(UIApplication *)application
and subtract the seconds in my SecondsLeft variable... How to Do this. ?
viewControll.h
#property(strong,nonatomic)NSTimer *timer;
viewController.m
int secondsLeft = 1800;
- (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
if (isLongPressed)
{
isLongPressed= FALSE;
}
else
{
isLongPressed= TRUE;
secondsLeft = minutes = seconds = 0;
if (timerr) {
[timerr invalidate];
}
timerr = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateCounter:) userInfo:nil repeats:YES];
secondsLeft=1800;
}
}
//Updates counter
- (void)updateCounter:(NSTimer *)theTimer
{
if(secondsLeft > 0 )
{
secondsLeft -- ;
//hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
myCounterLabel.text = [NSString stringWithFormat:#"%02d:%02d", minutes, seconds];
NSLog(#"the timer %# ",[NSString stringWithFormat:#"%02d:%02d", minutes, seconds] );
}
else if (secondsLeft ==0)
{
[timerr invalidate];
}
}
When entering background do:
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSDate date] forKey:#"TimeEnterBackground"];
[defaults synchronize];
After entering foreground again:
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSDate* enteringBackground = [defaults objectForKey:#"TimeEnterBackground"];
NSInteger timeInBackground = [[NSDate date] timeIntervalSince1970] - [enteringBackground timeIntervalSince1970];
This will work even if app will be turned off while being in background
As per apple developer guidelines you can't do "UI Updating on background"
You can store value any variable or object,When It will come on mainThread you can update your Label

How to set nstimer in reverse order

I have 3 UIlabel
First UILabel show- hh(hours)
Second UILabel show- mm(minutes)
Third UILabel show- ss(seconds)
Time is already showing in UILabels , but it has to run or tick reversely.
Means- 10:04:45 will come to 00:00:00--
This is what i am trying.
#pragma mark - Timer Function
-(NSArray *)getTimeFromSring:(NSString *)time
{
//only accepting time format hh:mm:ss
NSMutableArray *resultArr=[[NSMutableArray alloc]init];
int count=1;
for(int i=0;i<[time length];i++)
{
unichar ch = [time characterAtIndex: i];
if(ch!=':' && count<=3)
{
NSString *appendString = [NSString stringWithFormat:#"%c",ch];
[resultArr addObject:appendString];
}
else
{
count++;
}
}
return resultArr;
}
Finally
-(void)start:(NSTimer *)timer
{
if(_timer==nil)
{
startDate =[NSDate date];
_timer=[NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(timer:) userInfo:nil repeats:YES];
}
if(_timer!=nil)
{
float pauseTime = -1*[pauseStart timeIntervalSinceNow];
[_timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];
}
}
-(void)timer:(NSTimer *)timer
{
NSInteger secondsSinceStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:startDate];
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];
}
label.text=result;
NSLog(#"time interval -> %#",result);
}
-(void)stop
{
if(_timer!=nil)
{
endDate = [NSDate date];
NSLog(#"endate%#",endDate);
NSTimeInterval interval = [endDate timeIntervalSinceDate:startDate];
NSLog(#"total time %f",interval);
[_timer invalidate];
_timer = nil;
startDate=nil;
}
}
A couple of possible approaches, depending on the specifics of what you want to do:
Keep track of the elapsed time and subtract it from the start time.
Calculate the end time at the start and figure out the difference between the current time and then.
Take a look at these examples, maybe they will help you:)
https://github.com/mineschan/MZTimerLabel
https://github.com/TriggerTrap/TTCounterLabel

NSTimer Pause timer [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to Pause/Play NSTimer?
I have three buttons start stop and pause..My start and stop button is working fine with the below code..but when press Pause it pause the timer..but when again i Press start.IT continues from the new added time ...not from the pause time....
supoose i pause at 5 second of start and wait for 5 sec then press start...it should display 5 ...but displaying 10..
because I have not mentioned (timer:) in timer!=nill of start...
how it will be add..
I have problems:
Pause not working.
-(void)start:(NSTimer *)timer
{
if(_timer==nil)
{
startDate =[NSDate date];
_timer=[NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(timer:) userInfo:nil repeats:YES];
}
if(_timer!=nil)
{
float pauseTime = -1*[pauseStart timeIntervalSinceNow];
[_timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];
}
}
-(void)timer:(NSTimer *)timer
{
NSInteger secondsSinceStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:startDate];
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];
}
label.text=result;
NSLog(#"time interval -> %#",result);
}
-(void)stop
{
if(_timer!=nil)
{
startDate=nil;
[_timer invalidate];
_timer = nil;
}
}
-(void)pause:(NSTimer *)timer
{
pauseStart = [NSDate dateWithTimeIntervalSinceNow:0];
previousFireDate = [_timer fireDate];
[_timer setFireDate:[NSDate distantFuture]];
}
-(void)pause:(NSTimer *)timer
{
pauseStart = [NSDate dateWithTimeIntervalSinceNow:0];
previousFireDate = [_timer fireDate];
//[timer setFireDate:[NSDate distantFuture]];
[_timer setFireDate:[NSDate distantFuture]];
}
Here is the code that I used for a timer:
I store all the components for the time, in my (singleton) class, the hour, minute, seconds value.
And, I simply "invalidate" the timer in the "pause" method, AND I store the values.
See, if this helps.
-(void) startTimer
{
NSLog(#"Values for timer: %d H, %d M, %d S", self.hours, self.minutes, self.seconds);
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
}
-(void) pauseTimer
{
if(_timer)
{
[_timer invalidate];
}
_timer = nil;
self.hour = hourValue;
self.minute = minuteValue;
self.second = secondsValue;
}

How to make a persistent (multitasking-aware) stopwatch?

I am using NSTimer to make a stopwatch. I would like it to continue working if the user switches to a different app, but right now it only works when the app is running. I figure that this is probably pretty simple to fix by recording a timestamp when the timer starts, but I'm not really sure how to reconcile that with the ability to pause/restart the timer in the middle.
My code:
-(IBAction)start;
{
if(sharedInstance.timer == nil){
sharedInstance.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
sharedInstance.timerRunning = YES;
}
}
-(IBAction)stop;
{
[sharedInstance.timer invalidate];
sharedInstance.timer = nil;
sharedInstance.timerRunning = NO;
}
-(void)showActivity;
{
sharedInstance.elapsedTime += 1;
int hours = sharedInstance.elapsedTime / 3600 ;
int minutes = sharedInstance.elapsedTime / 60 - hours * 60;
int seconds = sharedInstance.elapsedTime - hours * 3600 - minutes * 60;
sharedInstance.timeString = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
self.readout.text = sharedInstance.timeString;
}
-(void)viewDidLoad {
sharedInstance = [DataSingleton sharedInstance];
if (sharedInstance.timeString==nil) {
sharedInstance.timeString=#"00:00:00";
}
self.readout.text = sharedInstance.timeString;
[sharedInstance.timer invalidate];
sharedInstance.timer = nil;
if(sharedInstance.timerRunning) {
[self start];
}
[super viewDidLoad];
}
-(void)reset {
[self stop];
sharedInstance.elapsedTime = 0;
sharedInstance.timeString = #"00:00:00";
self.readout.text = sharedInstance.timeString;
}
Every time you call showActivity you could get a new time stamp and subtract the old one from it getting the change in time. Then add that to elapsedTime instead of 1.

Resources