13

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];
}
Rahul Patel
  • 5,858
  • 6
  • 46
  • 72
Walker
  • 1,215
  • 2
  • 13
  • 26

3 Answers3

19

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

pjama
  • 3,014
  • 3
  • 26
  • 27
e.James
  • 116,942
  • 41
  • 177
  • 214
  • The countdownTimer is added to the run loop twice, which is wrong. – Nikolai Ruhe Jun 23 '09 at 14:32
  • @Nikolai Ruhe: Thank you for pointing that out. I have removed the incorrect code from my examples. – e.James Jun 23 '09 at 14:40
  • 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 Jun 23 '09 at 15:24
  • @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 Jun 23 '09 at 17:01
  • @nschmidt: I've changed it back to setIntegerValue. You're absolutely right about the added confusion. – e.James Jun 23 '09 at 17:05
  • This timer might be inaccurate. Check this answer for more info: http://stackoverflow.com/a/3519913/1226304 – derpoliuk Dec 17 '13 at 10:22
6

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
  • 2,383
  • 16
  • 22
-1

It looks like you're on the right track, but there are a few adjustments you need to make in order to achieve your countdown timer functionality. I'll guide you through the process step by step.

Firstly, let's address your questions:

A. How to limit the repeats to 60: You want the countdown to start from 60 seconds and go down to 0, so you don't actually need to limit the repeats to 60. Instead, you need to keep track of the number of seconds remaining and stop the timer when it reaches 0.

B. How to decrement the countdown in advanceTimer: In the advanceTimer: method, you should decrement the countdown value by 1 each time the timer fires. Additionally, you need to update the user interface (UI) to reflect the new countdown value. It seems like you're using a text field named countdown to display the countdown value.

Here's how you can modify your code to achieve your goal:

@interface YourViewController ()
@property (nonatomic, strong) NSTimer *countdownTimer;
@property (nonatomic) NSInteger countdownValue;
@end

@implementation YourViewController

- (IBAction)startCountdown:(id)sender
{
    self.countdownValue = 60; // Start countdown from 60 seconds
    [self updateCountdownUI]; // Update the UI initially
    
    // Invalidate any existing timer before starting a new one
    [self.countdownTimer invalidate];
    
    self.countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(advanceTimer:) userInfo:nil repeats:YES];
}

- (void)advanceTimer:(NSTimer *)timer
{
    if (self.countdownValue > 0) {
        self.countdownValue -= 1; // Decrement countdown value
        
        [self updateCountdownUI]; // Update the UI
        
        if (self.countdownValue == 0) {
            [self.countdownTimer invalidate]; // Stop the timer when countdown reaches 0
        }
    }
}

- (void)updateCountdownUI
{
    // Update your countdown text field with the current countdown value
    [countdown setIntegerValue:self.countdownValue];
}

@end

In this code, the countdownValue property is used to keep track of the remaining time. The updateCountdownUI method is responsible for updating the UI with the new countdown value. The advanceTimer: method decrements the countdown value and updates the UI accordingly. The timer is invalidated when the countdown reaches 0.

Remember to replace YourViewController with the actual name of your view controller class, and ensure that the IBOutlet for your countdown text field is correctly connected.