NSTimer not properly pausing and resuming - ios

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.

Related

How to set progress bar With timer after view controller loaded

I m using timer on circular progress bar and taken NSDate property in interface (tested also taking in viewWillAppear) but when viewController loads it show starting from 27 seconds remain, while i have set its value to 30 seconds.
My scenario is, a user has a question and optional answers and he/she has some time suppose 30 seconds when he/she click start button than another screen opens after this screen loads time start should be from 30 seconds.
#interface TestViewController ()
{
NSTimeInterval totalCountdownInterval;
NSDate* startDate;
}
}
-(void)viewWillAppear:(BOOL)animated{
[self startTimer];
totalCountdownInterval = 30.0;
startDate = [NSDate date];
}
-(void)startTimer {
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
}
}
-(void)stopTimer {
if ([_timer isValid]) {
[_timer invalidate];
}
_timer = nil;
}
-(void)timerFired:(NSTimer *)timer {
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSTimeInterval remainingTime = totalCountdownInterval - elapsedTime;
CGFloat timeRemain = remainingTime;
NSLog(#"%f", timeRemain);
if (remainingTime < 0.0) {
[_timer invalidate];
}
[_circleProgressBar setHintTextGenerationBlock:(^NSString *(CGFloat progress) {
if (!(timeRemain < 0.9)) {
progress = timeRemain - 1;
return [NSString stringWithFormat:#"%.0f", progress];
}else{
progress = 0;
return [NSString stringWithFormat:#"%.0f", progress];
}
})];
[_circleProgressBar setProgress:(_circleProgressBar.progress + 0.0333f) animated:YES];
}
-(void) viewWillDisappear:(BOOL)animated {
[self stopTimer];
}
Any help will be appreciated. Thanks.
The first timer event will be fired after 1 second is passed, so you should see 29 first. So you should display the number 30 manually somehow, outside the timer event.
And you subtract 1 in your code in setHintTextGenerationBlock :
progress = timeRemain - 1;
which will make you see 28 as initial.
And as final you should start timer in viewDidAppear instead of viewWillAppear where you lose another second and come to 27.
To correct code should be like:
#interface TestViewController ()
{
NSTimeInterval totalCountdownInterval;
NSDate* startDate;
}
}
-(void)viewDidLoad {
[_circleProgressBar setHintTextGenerationBlock:(^NSString *(CGFloat progress) {
return [NSString stringWithFormat:#"%.0f", progress * 30];
})];
[_circleProgressBar setProgress:1.0) animated:NO];
}
-(void)viewDidAppear:(BOOL)animated{
[self startTimer];
totalCountdownInterval = 30.0;
startDate = [NSDate date];
}
-(void)startTimer {
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
}
}
-(void)stopTimer {
if ([_timer isValid]) {
[_timer invalidate];
}
_timer = nil;
}
-(void)timerFired:(NSTimer *)timer {
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSTimeInterval remainingTime = totalCountdownInterval - elapsedTime;
CGFloat timeRemain = remainingTime;
NSLog(#"%f", timeRemain);
if (remainingTime < 0.0) {
[_timer invalidate];
}
[_circleProgressBar setProgress:(1.0 * timeRemain / 30.0) animated:YES];
}
-(void) viewWillDisappear:(BOOL)animated {
[self stopTimer];
}
You should set progress to 1.0 in the beginning and count down to 0.0 in 30 steps.
Set hint generation block to display something meaningful for 1.0 - 0.0 as 30 - 0.
use that timer code in void view did appear and try once.
- (void)viewDidAppear:(BOOL)animated
{
[self startTimer];
totalCountdownInterval = 30.0;
startDate = [NSDate date];
}

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

resume function does not work correctly in NSTime

I have a NSTimer that functions as a stopwatch. It makes the time count and has the pause function that works properly but the function does not work continue. The timer seems to be counting even paused but not updated the label and when I touch the button comes down to is to continue from where you left it continues counting that did not stop. How can I fix this?
this is my chronometer code:
- (void)viewDidLoad {
[super viewDidLoad];
[self setUp];
self.isPaused = NO;
}
- (void) setUp {
startDate = [NSDate date];
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0/100.0 target: self selector: #selector(timerUpdating) userInfo: nil repeats: true];
}
-(void)timerUpdating {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormater = [[NSDateFormatter alloc]init];
[dateFormater setDateFormat:#"mm:ss"];
[dateFormater setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [dateFormater stringFromDate:timerDate];
if (self.isPaused == NO){
_timerLabel.text = timeString;
}
}
- (IBAction)pauseAction:(id)sender {
[self pauseTimer:self.timer];
}
- (IBAction)resumeAction:(id)sender {
[self resumeTimer:self.timer];
}
-(void) pauseTimer:(NSTimer *)timer {
self.isPaused = YES;
self.pauseStart = [NSDate date];
self.previousFireDate = [timer fireDate];
[timer setFireDate:[NSDate distantFuture]];
}
-(void) resumeTimer:(NSTimer *)timer {
self.isPaused = 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);
}
At timerUpdating you set NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];. This will not take into account if the user paused the timer or not.
To correct this behavior you could do something like:
In .h (set a member variable):
NSTimeInterval pausedTime;
In .m (3 lines edited)
- (void)viewDidLoad {
[super viewDidLoad];
[self setUp];
self.isPaused = NO;
pausedTime = 0; //edit
}
- (void) setUp {
startDate = [NSDate date];
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0/100.0 target: self selector: #selector(timerUpdating) userInfo: nil repeats: true];
}
-(void)timerUpdating {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate] - pauseTime;//edit
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormater = [[NSDateFormatter alloc]init];
[dateFormater setDateFormat:#"mm:ss"];
[dateFormater setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [dateFormater stringFromDate:timerDate];
if (self.isPaused == NO){
_timerLabel.text = timeString;
}
}
- (IBAction)pauseAction:(id)sender {
[self pauseTimer:self.timer];
}
- (IBAction)resumeAction:(id)sender {
[self resumeTimer:self.timer];
}
-(void) pauseTimer:(NSTimer *)timer {
self.isPaused = YES;
self.pauseStart = [NSDate date];
self.previousFireDate = [timer fireDate];
[timer setFireDate:[NSDate distantFuture]];
}
-(void) resumeTimer:(NSTimer *)timer {
self.isPaused = NO;
NSDate *currentDate = [NSDate date];
NSTimeInterval pauseTime = [currentDate timeIntervalSinceDate:pauseStart];
pausedTime += pauseTime;//edit
NSDate *neededFireDate = [NSDate dateWithTimeInterval:pauseTime sinceDate:previousFireDate];
[timer setFireDate:neededFireDate];
NSLog(#"Fire Date: %#", timer.fireDate);
}

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?

Stopwatch start button resets timer

I am making a basic stopwatch, however, each time I start the timer, click stop, and start again wanting to continue my timer, the clock restarts at 0. I'm not quite sure what to do as I just picked up obj-c/Xcode.
#import "StopwatchViewController.h"
bool stopPressed = false;
bool startPressed = false;
int startsPressed = 0;
NSTimeInterval totalTimeInterval;
#interface StopwatchViewController ()
#property (strong, nonatomic) NSTimer *stopWatchTimer; // Store the timer that fires after a certain time
#property (strong, nonatomic) NSDate *startDate; // Stores the date of the click on the start button
#end
#implementation StopwatchViewController
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate: self.startDate];
totalTimeInterval = timeInterval;
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:totalTimeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat: #"HH:mm:ss.SSS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [dateFormatter stringFromDate:timerDate];
self.stopwatchLabel.text = timeString;
}
- (IBAction)onStartPressed:(id)sender
{
if(startsPressed < 1) {
if(startPressed) return;
startPressed = true;
stopPressed =false;
self.startDate = [NSDate date];
//create the stop watch timer that fires every 100ms
self.stopWatchTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0/100.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
} else {
startPressed = true;
stopPressed = false;
}
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)onStopPressed:(id)sender {
if(stopPressed) return;
stopPressed = true;
startPressed = false;
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
[self updateTimer];
}
- (IBAction)onResetPressed:(id)sender {
if(stopPressed == false) return;
self.stopWatchTimer = 0;
NSString *timeString = #"00:00:00.000";
self.stopwatchLabel.text = timeString;
}
#end
I am currently at this point where my start
#import "StopwatchViewController.h"
bool stopPressed = false;
bool startPressed = false;
int startsPressed = 0;
NSTimeInterval totalTimeInterval;
#interface StopwatchViewController ()
#property (strong, nonatomic) NSTimer *stopWatchTimer; // Store the timer that fires after a certain time
#property (strong, nonatomic) NSDate *startDate; // Stores the date of the click on the start button
#property (nonatomic, strong) NSDate *pauseDate;
#end
#implementation StopwatchViewController
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate: self.startDate];
totalTimeInterval = timeInterval;
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:totalTimeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat: #"HH:mm:ss.SSS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [dateFormatter stringFromDate:timerDate];
self.stopwatchLabel.text = timeString;
}
- (IBAction)onStartPressed:(id)sender
{
// if(startsPressed < 1) {
// if(startPressed) return;
// startPressed = true;
// stopPressed =false;
// self.startDate = [NSDate date];
//
// //create the stop watch timer that fires every 100ms
// self.stopWatchTimer =
// [NSTimer scheduledTimerWithTimeInterval:1.0/100.0
// target:self
// selector:#selector(updateTimer)
// userInfo:nil
// repeats:YES];
// } else {
//
// startPressed = true;
// stopPressed = false;
//
// }
if(startsPressed < 1) {
if( ! _startDate) {
self.startDate = [NSDate date];
}
else {
if(_pauseDate) {
NSTimeInterval startTime = _startDate.timeIntervalSince1970;
NSTimeInterval pauseTime = _startDate.timeIntervalSince1970;
// the actual elapsed time before we paused
NSTimeInterval elapsedTime = pauseTime - startTime;
// set a new start time to match our elapsed time.
NSTimeInterval currentTime = [NSDate date].timeIntervalSince1970;
NSTimeInterval newStartTime = currentTime - elapsedTime;
_startDate = [NSDate dateWithTimeIntervalSince1970:newStartTime];
_pauseDate = nil;
}
}
}
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)onStopPressed:(id)sender {
if(stopPressed) return;
_pauseDate = [NSDate date];
stopPressed = true;
startPressed = false;
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
[self updateTimer];
}
- (IBAction)onResetPressed:(id)sender {
if(stopPressed == false) return;
_startDate = nil;
_pauseDate = nil;
self.stopWatchTimer = 0;
NSString *timeString = #"00:00:00.000";
self.stopwatchLabel.text = timeString;
}
#end
Every time you hit "start", this code gets run:
self.startDate = [NSDate date];
This resets the start time you are using. Move that out somewhere so that it only happens once instead of on every time you start the timer and I think the result will be closer to what you want. Perhaps you can just store an extra BOOL that gets switched the first time the timer is started.
Edit:
Very good point brought up by Stonz2 that I overlooked. Doing it this way will cause the time to skip once you start it back up. In order to fix this you will need to store a separate NSDate when you stop the timer, to represent the "paused" time. Then, when you are starting the timer back up again, you will need to add the "paused" time to the "start" time so that the continuation is fluid.
Edit2:
I threw this into a project and here is some working code.
I added an extra property:
#property (nonatomic, strong) NSDate *pauseDate;
In onStopPressed:, I added this to initialize the pause time:
_pauseDate = [NSDate date];
Then, in onStartPressed:, I added the following code to ensure a single initialization of the startDate and to do the math to get the elapsed time after pausing:
// if we have a start date, don't initialize again
if(! _startDate)
{
self.startDate = [NSDate date];
}
else
{
if(_pauseDate)
{
NSTimeInterval startTime = _startDate.timeIntervalSince1970;
NSTimeInterval pauseTime = _pauseDate.timeIntervalSince1970;
// The actual elapsed time before we paused.
NSTimeInterval elapsedTime = pauseTime - startTime;
// Set a new start time to match our elapsed time.
NSTimeInterval currentTime = [NSDate date].timeIntervalSince1970;
NSTimeInterval newStartTime = currentTime - elapsedTime;
_startDate = [NSDate dateWithTimeIntervalSince1970:newStartTime];
_pauseDate = nil;
}
}
Also, in order to make your reset work correctly, in onResetPressed: you will need to add:
_startDate = nil;
_pauseDate = nil;
Just tested this and it works like a champ.
Edit3: full method as per comment thread
- (IBAction)onStartPressed:(id)sender
{
if(startsPressed < 1) {
if(startPressed) return;
startPressed = true;
stopPressed =false;
if(! _startDate)
{
self.startDate = [NSDate date];
}
else
{
if(_pauseDate)
{
NSTimeInterval startTime = _startDate.timeIntervalSince1970;
NSTimeInterval pauseTime = _pauseDate.timeIntervalSince1970;
NSTimeInterval elapsedTime = pauseTime - startTime;
NSTimeInterval currentTime = [NSDate date].timeIntervalSince1970;
NSTimeInterval newStartTime = currentTime - elapsedTime;
_startDate = [NSDate dateWithTimeIntervalSince1970:newStartTime];
_pauseDate = nil;
}
}
//create the stop watch timer that fires every 100ms
self.stopWatchTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0/100.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
} else {
startPressed = true;
stopPressed = false;
}
}
It is because you reset self.startDate in the onStartPressed method. Do this only in viewDidLoad: and in onResetPressed:

Resources