Stopwatch using NSTimer incorrectly includes paused time in display - objective-c

This is my code for an iPhone stopwatch. It works as expected and stops and resumes when the buttons are clicked.
When I hit "Stop", however, the timer won't stop running in the background, and when I hit "Start" to resume it, it will update the time and skip to where it is currently instead of resuming from the stopped time.
How can I stop the NSTimer? What is causing this to occur?
#implementation FirstViewController;
#synthesize stopWatchLabel;
NSDate *startDate;
NSTimer *stopWatchTimer;
int touchCount;
-(void)showActivity {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
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;
[dateFormatter release];
}
- (IBAction)onStartPressed:(id)sender {
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
touchCount += 1;
if (touchCount > 1)
{
[stopWatchTimer fire];
}
else
{
startDate = [[NSDate date]retain];
[stopWatchTimer fire];
}
}
- (IBAction)onStopPressed:(id)sender {
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self showActivity];
}
- (IBAction)reset:(id)sender; {
touchCount = 0;
stopWatchLabel.text = #"00:00.00";
}

Your calculation of the current display always uses the original start time of the timer, so the display after pausing includes the interval that the timer was paused.
The easiest thing to do would be to store another NSTimeInterval, say secondsAlreadyRun, when the timer is paused, and add that to the time interval you calculate when you resume. You'll want to update the timer's startDate every time the timer starts counting. In reset:, you would also clear out that secondsAlreadyRun interval.
-(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;
[dateFormatter release];
}
- (IBAction)onStartPressed:(id)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];
}
- (IBAction)onStopPressed:(id)sender {
// _Increment_ secondsAlreadyRun to allow for multiple pauses and restarts
secondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startDate];
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[startDate release];
[self showActivity];
}
- (IBAction)reset:(id)sender; {
secondsAlreadyRun = 0;
stopWatchLabel.text = #"00:00.00";
}
Don't forget to release that startDate somewhere appropriate! Also keep in mind that the documented NSTimer interface is for the method you give it to accept one argument, which will be the timer itself. It seems to work without that, but why tempt fate?
Finally, since you're using that NSDateFormatter so much, you might want to consider making it an ivar or put it in static storage in showActivity:, like so:
static NSDateFormatter * dateFormatter = nil;
if( !dateFormatter ){
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
}

So, when the user presses stop, and then start again, you aren't resetting the start time. But when you update the label, you are basing that on the total elapsed time from the original start time to the current time.
So if you run the timer for 10 seconds, stop, wait 10 seconds, and then start again, the timer will show 00:20.00 and start counting again from there.
What you want to do is reset the start time each time the user starts the clock, but then add the elapsed times of all previous runs as well. Or something similar.
BTW, you are leaking the start time every time you reset it now. Minor bug.
EDIT: looks like #Josh Caswell was thinking the same thing, but he types a LOT faster. :)

Are you using ARC or not?
If you are using ARC, it looks like you arent using a _strong reference. If you aren't using ARC, it doesn't looking you are retaining a reference to the timer.
I'm posting this from mobile so might be missing something.
EDIT: just noticed you were using release elsewhere, so I'll assume no ARC. You need to retain the timer after setting it to be able to access it later and invalidate.

You can use NSTimeInterval instead of timer. I have a functional code to pause and stop the timer.
#interface PerformBenchmarksViewController () {
int currMinute;
int currSecond;
int currHour;
int mins;
NSDate *startDate;
NSTimeInterval secondsAlreadyRun;
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
running = false;
}
- (IBAction)StartTimer:(id)sender {
if(running == false) {
//start timer
running = true;
startDate = [[NSDate alloc] init];
startTime = [NSDate timeIntervalSinceReferenceDate];
[sender setTitle:#"Pause" forState:UIControlStateNormal];
[self updateTime];
}
else {
//pause timer
secondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startDate];
startDate = [[NSDate alloc] init];
[sender setTitle:#"Start" forState:UIControlStateNormal];
running = false;
}
}
- (void)updateTime {
if(running == false) return;
//calculate elapsed time
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsed = secondsAlreadyRun + currentTime - startTime;
// extract out the minutes, seconds, and hours of seconds from elapsed time:
int hours = (int)(mins / 60.0);
elapsed -= hours * 60;
mins = (int)(elapsed / 60.0);
elapsed -= mins * 60;
int secs = (int) (elapsed);
//update our lable using the format of 00:00:00
timerLabel.text = [NSString stringWithFormat:#"%02u:%02u:%02u", hours, mins, secs];
//call uptadeTime again after 1 second
[self performSelector:#selector(updateTime) withObject:self afterDelay:1];
}
Hope this will help. Thanks

A timer class I created in Swift for a timer program in which a counter is updated every second from a set time. Answered to illustrate the Swift solution and the NSTimer function.
The timer can be stopped and restarted; it will resume from where it stopped. Events can be intercepted by the delegate for start, stop, reset, end and second events. Just check the code.
import Foundation
protocol TimerDelegate {
func didStart()
func didStop()
func didReset()
func didEnd()
func updateSecond(timeToGo: NSTimeInterval)
}
// Inherit from NSObject to workaround Selector bug
class Timer : NSObject {
var delegate: TimerDelegate?
var defaultDuration: NSTimeInterval?
var isRunning: Bool {
get {
return self.timer != nil && timer!.valid
}
}
private var secondsToGo: NSTimeInterval = 0
private var timer: NSTimer?
init(defaultDuration: NSTimeInterval, delegate: TimerDelegate? = nil) {
self.defaultDuration = defaultDuration
self.delegate = delegate
super.init()
}
func start() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateTimer", userInfo: nil, repeats: true)
self.timer!.tolerance = 0.05
if delegate != nil { delegate!.didStart() }
}
func stop () {
self.timer?.invalidate()
self.timer = nil
if delegate != nil { delegate!.didStop() }
}
func reset() {
self.secondsToGo = self.defaultDuration!
if delegate != nil { delegate!.didReset() }
}
func updateTimer() {
--self.secondsToGo
if delegate != nil { delegate!.updateSecond(self.secondsToGo) }
if self.secondsToGo == 0 {
self.stop()
if delegate != nil { delegate!.didEnd() }
}
}
}

Related

showing Count down timer in a label objective c

I am new to IOS.I want to user countdown timer.I mean If it set timer for 5 min then after 5 min a function should call so I am using NSTimer for that and I will show min and seconds in a label so after 5 min the label will start from 0 min so it is perfectly working when my app is in foreground when when my app go to background NSTimer will not work after 3 min so when ever I am going I am background I am save time and second in NSUserDefault and I am assigning that values to label when it comes to foreground but the timer is not showing perfectly
-(void) StartTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)timerTick:(NSTimer *)timer
{
timeSec++;
if (timeSec >= 60)
{
timeSec = timeSec-60;
timeMin++;
}
// else
// {
// [self StopTimer];
// }
//Format the string 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
//[timeLabel setStringValue:timeNow];
self.lblForTimer.text= timeNow;
NSNumber *numberValue = [NSNumber numberWithInt:[user.timerInterval intValue]];
if (timeMin>= [numberValue intValue])
{
timeMin = 0;
timeSec++;
}
}
//Call this to stop the timer event(could use as a 'Pause' or 'Reset')
- (void) StopTimer
{
[timer invalidate];
timer=nil;
timeSec = 0;
timeMin = 0;
//Since we reset here, and timerTick won't update your label again, we need to refresh it again.
//Format the string in 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
// [timeLabel setStringValue:timeNow];
self.lblForTimer.text= timeNow;
}
- (void)appWillEnterForeground:(NSNotification *)notification {
NSLog(#"will enter foreground notification");
}
- (void)applicationDidEnterBackground:(NSNotification *)notification {
NSLog(#"did enter backgroun notification");
NSLog(#"%d,%d",timeMin,timeSec);
NSDate *currentDate= [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:#"backgroundDate"];
[[NSUserDefaults standardUserDefaults] setInteger:timeSec forKey:#"sec"];
[[NSUserDefaults standardUserDefaults] setInteger:timeMin forKey:#"min"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self StopTimer];
}
- (void)appDidBecomeActive:(NSNotification *)notification {
NSDate *dateWhenAppGoesBg= (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:#"backgroundDate"];
NSTimeInterval timeSpentInBackground = [[NSDate date] timeIntervalSinceDate:dateWhenAppGoesBg];
int timeMinBack=floor(timeSpentInBackground/60);
int timeSecBack=round(timeSpentInBackground - timeMinBack * 60);
NSLog(#"%d%d",timeMinBack,timeSecBack);
// timeMin=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"min"]+timeMinBack;
timeMin=[user.counterValue intValue];
// timeSec=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"sec"]+timeSecBack;
if (timeMin>0) {
timeSec=timeSecBack;
}
else
{
timeSec=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"sec"]+timeSecBack;
}
NSLog(#"timme sec %d",timeSec);
[self StartTimer];
}
this is my code for implementing countdown timer its perfectly working in foreground but when it comes to forground from background timer values is nit perfect there is 1 min of gap between timer cananyone help me out in this issue
You can save currentDate as property. Don't need to use NSUserDefaults in this case.
Save and manipulate your timeout just in seconds and when needed retranslate as so:
NSInteger min = self.timeout / 60;
NSInteger sec = self.timeout % 60;

Time between two actions

Trying to record the time it takes for a user to press one button, and then the next.
Here's part of my .m file... I can't get theDate to hold its variable (The start time) All that happens is I get 0.000 seconds in the log & text label.
- (IBAction)start:(id)sender
{
NSDate *theDate = [[NSDate date] init];
}
- (IBAction)stop:(id)sender
{
NSTimeInterval timeInterval = [theDate timeIntervalSinceNow];
NSString *time = [NSString stringWithFormat:#"%f", timeInterval];
_timeLabel.text = time;
NSLog(time);
}
I also have
#property(retain, nonatomic) NSDate *theDate;
in the header file and
#synthesize theDate;
in the top of the .m file.
Thanks!
BTW I'm trying to go off of this post: Getting the time elapsed (Objective-c)
NSDate *theDate = [[NSDate date] init];
Creates a local variable so it doesn't use your property. In addition, as #Catfish_Man pointed out, [[NSDate date] init] should just be [NSDate date].
In addition, you should use self.theDate to use the property.
- (IBAction)start:(id)sender
{
self.theDate = [NSDate date];
}
- (IBAction)stop:(id)sender
{
NSTimeInterval timeInterval = [self.theDate timeIntervalSinceNow];
NSString *time = [NSString stringWithFormat:#"%f", timeInterval];
_timeLabel.text = time;
NSLog(time);
}
You are redefining date with a local scope in
- (IBAction)start:(id)sender
You should be using
self.date = [NSDate date];
You can get really fine timing (seconds.parts of seconds) using this StopWatch class. It uses the high-precision timer in the iPhone. Using NSDate will only get you second(s) accuracy.
StopWatch.h
#import <Foundation/Foundation.h>
#interface StopWatch : NSObject
{
uint64_t _start;
uint64_t _stop;
uint64_t _elapsed;
}
-(void) Start;
-(void) Stop;
-(void) StopWithContext:(NSString*) context;
-(double) seconds;
-(NSString*) description;
+(StopWatch*) stopWatch;
-(StopWatch*) init;
#end
StopWatch.m
#import "StopWatch.h"
#include <mach/mach_time.h>
#implementation StopWatch
-(void) Start
{
_stop = 0;
_elapsed = 0;
_start = mach_absolute_time();
}
-(void) Stop
{
_stop = mach_absolute_time();
if(_stop > _start)
{
_elapsed = _stop - _start;
}
else
{
_elapsed = 0;
}
_start = mach_absolute_time();
}
-(void) StopWithContext:(NSString*) context
{
_stop = mach_absolute_time();
if(_stop > _start)
{
_elapsed = _stop - _start;
}
else
{
_elapsed = 0;
}
NSLog([NSString stringWithFormat:#"[%#] Stopped at %f",context,[self seconds]]);
_start = mach_absolute_time();
}
-(double) seconds
{
if(_elapsed > 0)
{
uint64_t elapsedTimeNano = 0;
mach_timebase_info_data_t timeBaseInfo;
mach_timebase_info(&timeBaseInfo);
elapsedTimeNano = _elapsed * timeBaseInfo.numer / timeBaseInfo.denom;
double elapsedSeconds = elapsedTimeNano * 1.0E-9;
return elapsedSeconds;
}
return 0.0;
}
-(NSString*) description
{
return [NSString stringWithFormat:#"%f secs.",[self seconds]];
}
+(StopWatch*) stopWatch
{
StopWatch* obj = [[[StopWatch alloc] init] autorelease];
return obj;
}
-(StopWatch*) init
{
[super init];
return self;
}
#end
The class has a static stopWatch method that returns an autoreleased object.
Once you call start, use the seconds method to get the elapsed time. Call start again to restart it. Or stop to stop it. You can still read the time (call seconds) anytime after calling stop.
Example In A Function (Timing call of execution)
-(void)SomeFunc
{
StopWatch* stopWatch = [StopWatch stopWatch];
[stopWatch Start];
... do stuff
[stopWatch StopWithContext:[NSString stringWithFormat:#"Created %d Records",[records count]]];
}
There is an equivalent version in C++ (for .mm or .cpp implementations) if you need it. You can find that here.
Even if you don't use the class specifically, the general technique will allow you to get high accurate time differences for events.
NOTE I could cache the mach_timebase_info_data_t if it is the first time and save the call to get it. This is older code and does not have that (minor) optimization.

Unit testing for code like dispatch_async.

Need to do unit testing for the following code, dispatch_async means code won't be executed by app logic sequence, any idea on how to make it run timely?
Thank you.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[AdTracker dosomething];
});
See http://www.mikeash.com/pyblog/friday-qa-2011-07-22-writing-unit-tests.html
+ (BOOL)waitFor2:(finishBlock)block {
NSTimeInterval timeoutInSeconds = 10.0;
NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
while (!block() && ([giveUpDate timeIntervalSinceNow] > 0)) {
NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
[[NSRunLoop currentRunLoop] runUntilDate:stopDate]; // un-blocking.
DLog(#"+++++ %#", [NSDate date]);
}
return block();
}

How to create Countdown with 10th of a second?

HI I have already created a countdown in second, lets say 10 second, but I want to make it more precise
to 10.0 and display it on a label, how do it do that? Thanks in advance
This is what I have now for the "second" countdown
my NSTimer
counterSecond = 10
NSTimer timer1 = [NSTimer scheduledTimerWithTimeInterval : 1
Target:self selector:#selector (countLabel) userInfo:nil repeats:YES];
-(void)countLabel:
counterSecond --;
self.timerLabel.text = [NSString stringWithFormat #"%d", counterSecond];
I would use a start date/time to keep track of the countdown. Because iOS can delay firing the timer for other tasks.
- (void)countdownUpdateMethod:(NSTimer*)theTimer {
// code is written so one can see everything that is happening
// I am sure, some people would combine a few of the lines together
NSDate *currentDate = [NSDate date];
NSTimeInterval elaspedTime = [currentDate timeIntervalSinceDate:startTime];
NSTimeInterval difference = countdownSeconds - elaspedTime;
if (difference <= 0) {
[theTimer invalidate]; // kill the timer
[startTime release]; // release the start time we don't need it anymore
difference = 0; // set to zero just in case iOS fired the timer late
// play a sound asynchronously if you like
}
// update the label with the remainding seconds
countdownLabel.text = [NSString stringWithFormat:#"Seconds: %.1f", difference];
}
- (IBAction)startCountdown {
countdownSeconds = 10; // Set this to whatever you want
startTime = [[NSDate date] retain];
// update the label
countdownLabel.text = [NSString stringWithFormat:#"Seconds: %.1f", countdownSeconds];
// create the timer, hold a reference to the timer if we want to cancel ahead of countdown
// in this example, I don't need it
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector (countdownUpdateMethod:) userInfo:nil repeats:YES];
// couple of points:
// 1. we have to invalidate the timer if we the view unloads before the end
// 2. also release the NSDate if don't reach the end
}
counterSecond = 10
NSTimer timer1 = [NSTimer scheduledTimerWithTimeInterval : 0.1
Target:self selector:#selector (countLabel) userInfo:nil repeats:YES];
-(void)countLabel:
counterSecond - 0.1;
self.timerLabel.text = [NSString stringWithFormat #"%f", counterSecond];
That should work.

Comparing dates inside a nstimer selector

I am writing a countdowtimer application like the timer tab in the clock app of the iPhone. Right now I am having trouble comparing to dates, the 'now' and 'futureDate'.
all the variables are synthesized and are nonatomic and retain.
I have this code right now.
- (IBAction)startTimer:(id)sender {
NSLog(#"startTimer");
pickerView.hidden = YES;
labelView.hidden = NO;
now = [[NSDate alloc] init];
futureDate = [[NSDate alloc] initWithTimeInterval:picker.countDownDuration sinceDate:now];
NSLog(#"Dates.\nNow = (%#) \nfutureDate (%#)", now, futureDate);
timerLabelUpdater = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(labelUpdater) userInfo:nil repeats:YES];
}
- (void)labelUpdater {
if ([now timeIntervalSinceDate:futureDate] < 0.0) {
NSLog(#"YES\nDates.\nNow = (%#) \nfutureDate (%#)", now, futureDate);
} else {
NSLog(#"NO\nNow = (%#) \nfutureDate (%#)", now, futureDate);
}
}
Debugger info:
2011-02-08 16:46:02.449 App[22504:207] startTimer
2011-02-08 16:46:02.451 App[22504:207] Dates.
Now = (2011-02-08 18:46:02 +0000)
futureDate (2011-02-08 18:47:02 +0000)
2011-02-08 16:46:03.451 App[22504:207] YES
And it stay giving me yes "forever".
But if you see the variable hours, they are +2h in the future compared from my clock time. Is this the bug?
Sorry my if was testing the wrong date, comparing the variable now and futureDate the time interval will be the same forever. I replaced the now variable for a [[NSDate alloc] init] in the test. Right now the code works.
if ([[[NSDate alloc]init] timeIntervalSinceDate:futureDate] < 0.0) {
NSLog(#"YES\nDates.\nNow = (%#) \nfutureDate (%#)", now, futureDate);
} else {
NSLog(#"NO\nNow = (%#) \nfutureDate (%#)", now, futureDate);
}
sorry for the stupid question.