Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Countdown Timer

I'm trying to create a countdown timer that takes countdown, an IBOutlet connected to a textfield, from 60 seconds down to 0. I'm not sure

A. How to limit the repeats to 60 and

B. How to decrement the countdown in advanceTimer:

- (IBAction)startCountdown:(id)sender
{
    NSTimer *countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self     selector:@selector(advanceTimer:) userInfo:nil repeats:YES];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:countdownTimer forMode:NSDefaultRunLoopMode];
}

- (void)advanceTimer:(NSTimer *)timer
{
    [countdown setIntegerValue:59];
}
like image 430
Walker Avatar asked Jun 23 '09 14:06

Walker


2 Answers

You're on the right track so far.

Sticking with the code you already have, here is how advanceTimer method should look to make it work:

- (void)advanceTimer:(NSTimer *)timer
{
    [countdown setIntegerValue:([countdown integerValue] - 1)];
    if ([countdown integerValue] == 0)
    {
        // code to stop the timer
    }
}

edit: To make the whole thing more object-oriented, and to avoid converting from strings to numbers and back every time, I would instead do something like this:

// Controller.h:
@interface Controller
{
    int counter;
    IBOutlet NSTextField * countdownField;
}
@property (assign) int counter;
- (IBAction)startCountdown:(id)sender;
@end

// Controller.m:
@implementation Controller

- (IBAction)startCountdown:(id)sender
{
    counter = 60;

    NSTimer *countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                         target:self
                                       selector:@selector(advanceTimer:)
                                       userInfo:nil
                                        repeats:YES];
}

- (void)advanceTimer:(NSTimer *)timer
{
    [self setCounter:(counter -1)];
    [countdownField setIntegerValue:counter];
    if (counter <= 0) { [timer invalidate]; }
}

@end

And, if you can make use of bindings, you could simply bind the text field's intValue to the counter property of the Controller. This would allow you to elminate the IBOutlet in the class interface, and the setIntegerValue: line in advanceTimer.

update: Removed the code which adds the timer to the run loop twice. Thank you to Nikolai Ruhe and nschmidt for noticing that error.

update: Used the setIntegerValue method to simplify the code, as per nschmidt.

edit: Typo in definition of (void)advanceTimer:(NSTimer *)timer ... caused annoying 'unrecognized selector sent to instance' exception

like image 145
e.James Avatar answered Oct 17 '22 02:10

e.James


You can add a instance variable int _timerValue to hold the timer value and then do the following. Also note that the NSTimer you are creating is already scheduled on the current run loop.

- (IBAction)startCountdown:(id)sender
{
    _timerValue = 60;
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(advanceTimer:) userInfo:nil repeats:NO];
}

- (void)advanceTimer:(NSTimer *)timer
{
    --_timerValue;
    if(self.timerValue != 0)
       [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(advanceTimer:) userInfo:nil repeats:NO];

    [countdown setIntegerValue:_timerValue];
}
like image 30
nschmidt Avatar answered Oct 17 '22 01:10

nschmidt