What is wrong with my countdown timer method? - iphone

Trying to make a countdown timer from a given NSTimeInterval, the label doesn't seem to be updating.
- (IBAction)startTimer:(id)sender{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
}
- (void)timerAction:(NSTimer *)t {
if(testTask.timeInterval == 0){
if (self.timer){
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
else {
testTask.timeInterval--;
}
}
NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
NSString *string = [NSString stringWithFormat:#"%02u:%02u:%02u",
seconds / 3600, (seconds / 60) % 60, seconds % 60];
timerLabel.text = string;
}

Issue is, you are decrementing the testTask.timeInterval inside the if(testTask.timeInterval == 0) , this condition never evaluates to true (because you set it to 10). That is why there is no change in the label.
You need to put that else case after the first if statement (Currently you placed it under second if statement).
You need to write your method like:
-(void)timerAction:(NSTimer *)t
{
if(testTask.timeInterval == 0)
{
if (self.timer)
{
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
}
else
{
testTask.timeInterval--;
}
NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
NSString *string = [NSString stringWithFormat:#"%02u:%02u:%02u",
seconds / 3600, (seconds / 60) % 60, seconds % 60];
timerLabel.text = string;
}

I believe your if statements are nested incorrectly. Move your else statement to the outermost 'if' like so.
if(testTask.timeInterval == 0){
if (self.timer){
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
} else {
testTask.timeInterval--;
}

Related

NSTimer countdown skips last second (not using last milliseconds)

I have a NS timer countdown which works perfectly fine thanks to your answers. However, my timer skips the last second so that it does not count the last 0s:99 ms . Is there anything wrong with my code? Best regards!
-(void) setTimer {
MySingletonCenter *tmp = [MySingletonCenter sharedSingleton];
tmp.milisecondsCount = 99;
tmp.secondsCount = 2;
tmp.countdownTimerGame = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
-(void) timerRun {
MySingletonCenter *tmp = [MySingletonCenter sharedSingleton];
tmp.milisecondsCount = tmp.milisecondsCount - 1;
if(tmp.milisecondsCount == 0){
tmp.secondsCount -= 1;
if (tmp.secondsCount == 0){
//Stuff for when the timer reaches 0
[tmp.countdownTimerGame invalidate];
tmp.countdownTimerGame = nil;
tmp.lives = tmp.lives - 1;
NSString *newLivesOutput = [NSString stringWithFormat:#"%d", tmp.lives];
livesLabel.text = newLivesOutput;
if (tmp.lives == 0) {
timeLabel.text = #"0:00";
[self performSelector:#selector(stopped) withObject:nil afterDelay:2.0];
}
else {[self setTimer]; }
}
else
tmp.milisecondsCount = 99;
}
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%2d", tmp.secondsCount, tmp.milisecondsCount];
timeLabel.text = timerOutput;
}
try to change this
-(void) setTimer {
MySingletonCenter *tmp = [MySingletonCenter sharedSingleton];
tmp.milisecondsCount = 100;// Change this from 99 to 100
tmp.secondsCount = 2;
tmp.countdownTimerGame = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
For a start, get rid of
else {[self setTimer]; }
Otherwise you are restarting the timer every time that selector fires.
Next: swap these two around
tmp.secondsCount -= 1;
if (tmp.secondsCount == 0){
to
else {
tmp.secondsCount -= 1;
tmp.milisecondsCount = 99;
}
like so:
-(void) timerRun {
MySingletonCenter *tmp = [MySingletonCenter sharedSingleton];
tmp.milisecondsCount = tmp.milisecondsCount - 1;
if(tmp.milisecondsCount == 0){
if (tmp.secondsCount == 0){
//Stuff for when the timer reaches 0
[tmp.countdownTimerGame invalidate];
tmp.countdownTimerGame = nil;
tmp.lives = tmp.lives - 1;
NSString *newLivesOutput = [NSString stringWithFormat:#"%d", tmp.lives];
livesLabel.text = newLivesOutput;
if (tmp.lives == 0) {
timeLabel.text = #"0:00";
[self performSelector:#selector(stopped) withObject:nil afterDelay:2.0];
}
}
else
{
tmp.milisecondsCount = 99;
tmp.secondsCount -= 1;
}
}
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%2d", tmp.secondsCount, tmp.milisecondsCount];
timeLabel.text = timerOutput;
}

why am i getting negative values for my timeinterval?

I have no idea what is happening. I am trying to make a timer that works by comparing dates. When I start the timer, sometimes it works and then randomly stops, and sometimes it just returns the value of the timeInterval. It seems to only work correctly when the time interval is negative. This is my method:
-(IBAction)startTimer:(id)sender{
if (timer == nil) {
[startButton setTitle:#"Start" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
date = [NSDate dateWithTimeIntervalSinceNow:testTask.timeInterval]; //instance variable
} else {
[startButton setTitle:#"Stop" forState:UIControlStateNormal];
[timer invalidate];
timer = nil;
}
}
-(void)timerAction:(NSTimer *)t
{
NSDate *currentDate = [NSDate date];
NSTimeInterval updatedTimeInterval = [date timeIntervalSinceDate:currentDate];
if (updatedTimeInterval > 0){
if (self.timer)
{
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
}
else
{
testTask.timeInterval = updatedTimeInterval;
NSLog(#"%.2f", testTask.timeInterval);
NSError *error;
if (![self.context save:&error]) {
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
}
NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
NSString *string = [NSString stringWithFormat:#"%02u:%02u:%02u",
seconds / 3600, (seconds / 60) % 60, seconds % 60];
timerLabel.text = string;
NSLog(#"%f", testTask.timeInterval);
}
You're subtracting the later date, currentDate, from the earlier date. I told you to do it the other way around: [[NSDate date] timeIntervalSinceDate:date]
There's a reason timeIntervalSinceNow is not called timeIntervalUntilNow...
Also known as: the time elapsed since now is negative if the date in question is earlier than the current date/time. It would be positive if the date was later in time than the current date. (Just a bit of logic and/or English semantics :))
Earlier date Current date
^ ^
+------------------+
since now: (earlier date) - (current date)... that's < 0

Implementing a Countdown Timer in Objective-c?

I am new to iOS programming. I am working on words matching game. In this game I need to implement time counter which shows minutes and seconds. I want when my game is started my timer to start with 3 minutes. Now I want to decrease this timer in reverse direction with seconds. my code just work for seconds..here is my code:
secondsLeft--;
int seconds = (secondsLeft %3600) % 60;
Timerlbl.text = [NSString stringWithFormat:#"%02d",seconds];
if (seconds==0)
{
UIAlertView *pAlert = [[UIAlertView alloc]initWithTitle:#"Sorry!!"
message:#"TimeOver" delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:#"Cancel",nil];
[pAlert show];
[pAlert release];
}
}
In Viewdidload i call it through timer..
countDown=[NSTimer scheduledTimerWithTimeInterval:5.0 target:self
selector:#selector(TimeOver) userInfo:nil repeats:YES];
pleas any one guide me how can i do it in both minutes and seconds.
You can do it like this(ARC Enabled):-
#interface ViewController()
{
UILabel *progress;
NSTimer *timer;
int currMinute;
int currSeconds;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
progress=[[UILabel alloc] initWithFrame:CGRectMake(80, 15, 100, 50)];
progress.textColor=[UIColor redColor];
[progress setText:#"Time : 3:00"];
progress.backgroundColor=[UIColor clearColor];
[self.view addSubview:progress];
currMinute=3;
currSeconds=00;
// Do any additional setup after loading the view, typically from a nib.
}
-(void)start
{
timer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:nil repeats:YES];
}
-(void)timerFired
{
if((currMinute>0 || currSeconds>=0) && currMinute>=0)
{
if(currSeconds==0)
{
currMinute-=1;
currSeconds=59;
}
else if(currSeconds>0)
{
currSeconds-=1;
}
if(currMinute>-1)
[progress setText:[NSString stringWithFormat:#"%#%d%#%02d",#"Time : ",currMinute,#":",currSeconds]];
}
else
{
[timer invalidate];
}
}
I know this is old question, but I want to share my way to achieve this. We have method (timeIntervalSinceDate:) to calculate the interval and have fixed amount of seconds to count down. In my case 300 s.
UPDATE JULY, 2015 SWIFT
#IBAction func startTimer(sender: UIButton) {
sender.selected = !sender.selected;
//if selected fire timer, otherwise stop
if (sender.selected) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("updateTimer"), userInfo: nil, repeats: true);
self.startDate = NSDate();
} else {
self.stopTimer();
}
}
func stopTimer() {
self.timer.invalidate();
}
func updateTimer() {
// Create date from the elapsed time
var currentDate:NSDate = NSDate();
var timeInterval:NSTimeInterval = currentDate.timeIntervalSinceDate(self.startDate!);
//300 seconds count down
var timeIntervalCountDown = 300 - timeInterval;
var timerDate:NSDate = NSDate(timeIntervalSince1970: timeIntervalCountDown);
// Create a date formatter
var dateFormatter = NSDateFormatter();
dateFormatter.dateFormat = "mm:ss";
dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0);
// Format the elapsed time and set it to the label
var timeString = dateFormatter.stringFromDate(timerDate);
self.timerLabel?.text = timeString;
}
Working github swift sample project
Objective-C
- (void)updateTimer
{
// Create date from the elapsed time
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate
timeIntervalSinceDate:self.startDate];
NSLog(#"time interval %f",timeInterval);
//300 seconds count down
NSTimeInterval timeIntervalCountDown = 300 - timeInterval;
NSDate *timerDate = [NSDate
dateWithTimeIntervalSince1970:timeIntervalCountDown];
// Create a date formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
// Format the elapsed time and set it to the label
NSString *timeString = [dateFormatter stringFromDate:timerDate];
self.stopWatch.text = timeString;
}
- (void)stopTimer
{
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
[self updateTimer];
}
- (void)startTimer
{
if (self.stopWatchTimer) {
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
}
self.startDate = [NSDate date];
// Create the stop watch timer that fires every 100 ms
self.stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
UPDATE:
it is extremely expensive to create a NSDateFormatter each time you pass through updateTimer. Far better to create the NSDateFormatter outside the loop and reuse it. Credits: #siburb
In .h file
IBOutlet UILabel* lblTimer;
NSTimer* gameTimer;
In .m file
-(void)viewDidLoad
{
[super viewDidLoad];
[self createTimer];
}
- (void)createTimer
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
gameTimer = [[NSTimer timerWithTimeInterval:1.00 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
timeCount = 20;
[runLoop run];
[pool release];
}
- (void)timerFired:(NSTimer *)timer
{
if(timeCount == 0)
{
[self timerExpired];
}
else
{
timeCount--;
if(timeCount == 0) {
[timer invalidate];
[self timerExpired];
}
}
lblTimer.text = [NSString stringWithFormat:#"%02d:%02d",timeCount/60, timeCount % 60];
self.title = lblTimer.text;
}
Bind the above Label Outlet with Label in View.
It is working correctly with Me
Simple and less code in Objective-C,
in .h file create objects for Timer and totalSeconds
NSTimer *timer;
int totalSeconds;
.m file
//you can call this method where you want
[self startTimer];
-(void)startOtpTimer {
totalSeconds = 120;//your time
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:#selector(updateCountDownTime) userInfo:nil repeats:YES];
}
-(void)updateCountDownTime {
[self populateLabelwithTime:totalSeconds];
if( totalSeconds != 0) {
totalSeconds -= 1;
} else {
[timer invalidate];//stop timer
self.timeLabel.text = #"";//after reach 0 you can set empty string
}
}
- (void)populateLabelwithTime:(int)seconds {
self.timeLabel.text = [NSString stringWithFormat:#"%02d", totalSeconds];
}
You can use NSTimer to achieve this.

nstimer stop when touch the screen

I have a timer class, with a nstimer inside, who push notification to a uitableviewcontroller, and the notification object(value of the timer) is affected in a uitableviewcell. My problem is when I touch the screen to scroll, the timer stop, NSTimer don't start in a other thread? How I can fix this problem.
My timerClass
#import "timerEtape.h"
#import "FonctionUtile.h"
#import "Mission.h"
static timerEtape *sngTimerEtape = nil;
#implementation timerEtape
#synthesize repeatingTimerEtape;
#synthesize dateComp;
#synthesize questionCircuit;
#synthesize b_Pause;
+(timerEtape *) singletonTimer
{
#synchronized(self){
if (sngTimerEtape == nil )
{
sngTimerEtape = [[timerEtape alloc]init];
}
}
return sngTimerEtape;
}
-(id)init
{
self = [super init];
if (self != nil) {
dateComp = [[NSDateComponents alloc] init];
b_Pause = FALSE;
}
return self;
}
- (void)startTimer {
[repeatingTimerEtape invalidate];
NSString * temps;
if (questionCircuit) {
temps = [[Mission singletonMission].circuitTerrain valeurChampEtapeCircuitEnCours:#"et_temps_etape" :FALSE];
}
else {
temps = [[Mission singletonMission].histoireTerrain valeurChampQuestion:#"hi_temps_etape" :FALSE];
}
if (!b_Pause) {
[dateComp setHour:0];
[dateComp setMinute:[[FonctionUtile gauche:temps :2] intValue]];
[dateComp setSecond:[[FonctionUtile droite:temps :2] intValue]];
}
else {
b_Pause = FALSE;
}
self.repeatingTimerEtape = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateLabelTimerEtape:) userInfo:nil repeats:YES];
}
-(void)pause
{
self.b_Pause = TRUE;
[repeatingTimerEtape invalidate];
}
-(void)updateLabelTimerEtape:(NSTimer*)theTimer
{
if ([dateComp second] == 0) {
if ([dateComp minute] == 0) {
if ([dateComp hour] != 0) {
[dateComp setHour:[dateComp hour] -1];
[dateComp setMinute:59];
[dateComp setSecond:59];
}
else {
[repeatingTimerEtape invalidate];
[delegate performSelector:touchAction];
}
}
else {
[dateComp setMinute:[dateComp minute] -1];
[dateComp setSecond:59];
}
}
else {
[dateComp setSecond:[dateComp second] -1];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"rafraichirTimerEtape" object:[NSString stringWithFormat:#"%02d:%02d", [dateComp minute],[dateComp second]]];
}
-(void)setTarget:(id)target andAction:(SEL)action {
delegate = target;
touchAction = action;
}
-(void)dealloc
{
[dateComp release];
[repeatingTimerEtape release];
[super dealloc];
}
#end
and when I receive the notification in my uitableviewcontroller class
-(void)rafraichirTimerEtape:(NSNotification*)notification
{
[tempsRestant release];
tempsRestant = [[notification object]copy];
[table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]].detailTextLabel.text = [notification object];
}
thanx
I have encountered with this problem before. In fact, NSTimer does not run in another thread, it always run in the thread which it is started. There is a runloop concept in iphone sdk, you should read it from here to understand timers. In this concept, you push some jobs into runloop and runloop implements them sequentially. The main thread is always a run loop and ui jobs are run there. So if you start your timer in the main thread, it will be affected by ui processing. You should start a new thread, configure it as a run loop and start your timers there.
Edit: Here is a sample code from the link that I posted. threadMain is the first function which the thread starts.
- (void) threadMain
{
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
if you want runloop to run infinitely, but terminate it with a condition, use this instead
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(doFireTimer:) userInfo:nil repeats:YES];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]);

NSTimer - Stopwatch

I've trying to create a stopwatch with HH:MM:SS, code is as follows:
-(IBAction)startTimerButton;
{
myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
}
-(IBAction)stopTimerButton;
{
[myTimer invalidate];
myTimer = nil;
}
-(void)showActivity;
{
int currentTime = [time.text intValue];
int newTime = currentTime + 1;
time.text = [NSString stringWithFormat:#"%.2i:%.2i:%.2i", newTime];
}
Although the output does increment by 1 second as expected the format of the output is XX:YY:ZZZZZZZZ , where XX are the seconds.
Anyone any thoughts ??
Your stringWithFormat asks for 3 integers but you're only passing in one ;)
Here is some code that I've used before to do what I think you're trying to do :
- (void)populateLabel:(UILabel *)label withTimeInterval:(NSTimeInterval)timeInterval {
uint seconds = fabs(timeInterval);
uint minutes = seconds / 60;
uint hours = minutes / 60;
seconds -= minutes * 60;
minutes -= hours * 60;
[label setText:[NSString stringWithFormat:#"%#%02uh:%02um:%02us", (timeInterval<0?#"-":#""), hours, minutes, seconds]];
}
to use it with a timer, do this :
...
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTimer:) userInfo:nil repeats:YES];
...
- (void)updateTimer:(NSTimer *)timer {
currentTime += 1;
[self populateLabel:myLabel withTimeInterval:time;
}
where currentTime is a NSTimeInterval that you want to count up by one every second.