NSTimer countdown not functioning correctly - ios

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

Related

NSTimer start and call method every 2 seconds and stop after 3 minutes

i know how to start NSTimer and i given the code for this but now i want to stop the NSTimer after 3 or 4 minutes but how can i do this
i know how to give NSTimer but how to stop after 3 minutes
need some help
NSTimer* myTimer;
myTimer= [NSTimer scheduledTimerWithTimeInterval: 2.0 target: self
selector: #selector(updateUIinMainThread:) userInfo: nil repeats: YES];
Declaration :
int totalSeconds;
NSTimer *twoMinTimer;
Code :
- (void)timer {
totalSeconds--;
if ( totalSeconds == 0 ) {
[twoMinTimer invalidate];
//Timer stops after 2 minute from this you can do your stuff here
}
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
totalSeconds = 120;
twoMinTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timer)
userInfo:nil
repeats:YES];
}
Hope this helps
Save the time when the timer is initiated
NSTimer* myTimer;
myTimer= [NSTimer scheduledTimerWithTimeInterval: 2.0 target: self
selector: #selector(updateUIinMainThread:) userInfo: nil repeats: YES];
savedTime = [NSDate date];
and in function updateUIinMainThread: compare current time to saved time. If the result is greater than 180 seconds, stop the timer.
-(void)updateUIinMainThread:(NSTimer *)timer
{
NSDate *timeNow = [NSDate date];
NSTimeInterval timespan = [timeNow timeIntervalSinceDate:savedTime];
if(timesapn>180)
{
[timer invalidate];
}
}
May be the following code will help you:
step 1: declare following instance variables
#interface yourclass : NSObject
{
NSTimer* myTimer;
NSDate *initialDate;
}
step 2: create myTimer in your class where you want:
myTimer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(callByTimer) userInfo:nil repeats:YES];
step 3: implement callByTimer method:
- (void)callByTimer{
// get initial date at very first call by myTimer
if (!initialDate)
{
initialDate = [NSDate date];
}
// get current date
NSDate *currentDate = [NSDate date];
// get seconds between currentdate and initial date
NSTimeInterval secondsBetween = [currentDate timeIntervalSinceDate:initialDate];
// convert seconds into minutes
NSInteger minutes = secondsBetween/60;
// check if minutes is greater than or equal to 3 then invalidate myTimer and assign nil to initialDate variable
if (minutes>=3)
{
[myTimer invalidate];
initialDate = nil;
}}

Persistent allocation of Heap Memory NSTimer

I discovered an issue with my sample project. I simply create a scheduledTimer that "animate" a label and then, when I reached the result I want, I invalidate the timer and I set an another one, this time as a "clock".
This is the code I use
//
// ViewController.m
//
//
//
//
//
#import "ViewController.h"
#define ANIMATION_INTERVAL 0.07 // in secondi
#define ANIMATION_DURATION 1 // in secondi
#interface ViewController ()
{
int contatore;
NSString *hour;
NSString *minute;
NSString *second;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
contatore = 0;
[self startTimeAnimation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)startTimeAnimation
{
NSTimer * animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_INTERVAL target:self selector:#selector(timeout:) userInfo:nil repeats:YES];
}
-(void)timeout: (NSTimer *)timer
{
// Simulate of the Timer Duration with a counter
if (contatore < ceilf(ANIMATION_DURATION/ANIMATION_INTERVAL))
{
// Proceed with animation
contatore++;
int tempHour = arc4random_uniform(24);
int tempMinute = arc4random_uniform(60);
int tempSecond = arc4random_uniform(60);
if (tempHour < 10)
{
hour = [NSString stringWithFormat:#"0%d", tempHour];
}
else
{
hour = [NSString stringWithFormat:#"%d", tempHour];
}
if (tempMinute < 10)
{
minute = [NSString stringWithFormat:#"0%d", tempMinute];
}
else
{
minute = [NSString stringWithFormat:#"%d", tempMinute];
}
if (tempSecond < 10)
{
second = [NSString stringWithFormat:#"0%d", tempSecond];
}
else
{
second = [NSString stringWithFormat:#"%d", tempSecond];
}
_orarioLbl.text = [NSString stringWithFormat:#"%#:%#:%#", hour, minute, second];
}
else
{
// Stops animation
[timer invalidate];
[timer release];
contatore = 0;
[self setActualTime];
// Starts clock
NSTimer *clockTimer = [NSTimer timerWithTimeInterval:.5f target:self selector:#selector(updateClock) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:clockTimer forMode:NSRunLoopCommonModes];
}
}
-(void)updateClock
{
[self setActualTime];
}
-(void)setActualTime
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH"];
hour = [dateFormatter stringFromDate:[NSDate date]];
[dateFormatter setDateFormat:#"mm"];
minute = [dateFormatter stringFromDate:[NSDate date]];
[dateFormatter setDateFormat:#"ss"];
second = [dateFormatter stringFromDate:[NSDate date]];
_orarioLbl.text = [NSString stringWithFormat:#"%#:%#:%#", hour, minute, second];
[dateFormatter release];
}
#end
Since I start the "clock", memory still stays on 19/20 MB with constant persistent allocation. When the timer updates the minute value, persistent allocations increase as you can see in the gif! How is it possible? However, also 19MB of memory is too much for a simple clock, isn't it?
Profiling on Instruments, the new allocations are all about CoreGraphics!
EDIT I tested it on another device and the persistent allocations decreased. I don't know why, but I solved in this way. Thanks
NSTimer retains its target and that may lead to a retain cycle. Grab a weak reference to self and set that as the target:
__weak typeof(self) weakSelf = self;
NSTimer * animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_INTERVAL target:weakSelf selector:#selector(timeout:) userInfo:nil repeats:YES];
and
__weak typeof(self) weakSelf = self;
NSTimer *clockTimer = [NSTimer timerWithTimeInterval:.5f target:weakSelf selector:#selector(updateClock) userInfo:nil repeats:YES];
May I also recommend you create a property for the dateFormatter, as NSDateFormatters are expensive to create. And why are you releasing the dateFormatter? Are you not using ARC?

NSTimer not properly pausing and resuming

I have an NSTimer which I pause and resume with the following methods respectively:
-(void) pauseTimer:(NSTimer *)timer {
self.scene.paused = YES;
pauseStart = [NSDate date];
previousFireDate = [timer fireDate];
[timer setFireDate:[NSDate distantFuture]];
}
-(void) resumeTimer:(NSTimer *)timer {
self.scene.paused = NO;
NSDate *currentDate = [NSDate date];
NSTimeInterval pauseTime = [currentDate timeIntervalSinceDate:pauseStart];
NSDate *neededFireDate = [NSDate dateWithTimeInterval:pauseTime sinceDate:previousFireDate];
[timer setFireDate:neededFireDate];
NSLog(#"Fire Date: %#", timer.fireDate); //returning null
}
It is not properly resetting the fire date because when I check timer.fireDate, it says null. All we want is for these methods to pause the timer and resume the timer where it left off when the resume method is called. Any help is greatly appreciated.
My solution differs from yours in one major thing. I recreate a timer when it resumes. Here is how I do it and it's been working quite well:
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, assign) NSTimeInterval elapsedTime;
#property (nonatomic, strong) NSDate *startDate;
- (void)startTimer
{
NSTimeInterval timeInterval = 20.0f;
// Create a timer. Adjust the elapsed time.
_timer = [NSTimer scheduledTimerWithTimeInterval:(timeInterval - _elapsedTime)
target:self
selector:#selector(timerFireMethod:)
userInfo:nil
repeats:YES];
// Save the start date.
_startDate = [NSDate date];
}
- (void)timerFireMethod:(NSTimer *)theTimer
{
// Clean.
[_timer invalidate];
_timer = nil;
_elapsedTime = 0.0;
[self startTimer];
// Additional handling.
}
- (void)pauseTimer
{
// Clean.
[_timer invalidate];
_timer = nil;
// Save the elapsed time.
_elapsedTime = [[NSDate date] timeIntervalSinceDate:_startDate];
}
Hope it helps.

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

Resources