views:

513

answers:

2
+3  Q: 

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];
}
+7  A: 

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)advaceTimer:(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.

e.James
The countdownTimer is added to the run loop twice, which is wrong.
Nikolai Ruhe
@Nikolai Ruhe: Thank you for pointing that out. I have removed the incorrect code from my examples.
e.James
I guess setIntegerValue is more efficient than [NSString stringWithFormat:], so I would not have made this "optimization". Especially since it doesn't help the clarity of the code.
nschmidt
@nschmidt: I'm sure you're right. I wasn't sure about the usage, and I don't have a compiler in front of me, so I just went with what I knew would work.
e.James
@nschmidt: I've changed it back to setIntegerValue. You're absolutely right about the added confusion.
e.James
+4  A: 

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