3
votes

So I am trying to set up a basic timer but I am failing miserably. Basically all I want is to start a 60 second timer when the user clicks a button, and to update a label with the time remaining(like a countdown). I created my label and button and connected them in IB. Next I created a IBAction for the button. Now when I tried to update the label based on the timer, my app screws up. Here's my code:

NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 1
                      target: self
                      selector:@selector(updateLabelDisplay)
                      userInfo: nil repeats:YES];

I also have an updateLabelDisplay function that determines how many times the timer has ran and then subtracted that number from 60 and displays that number in the countdown label. Can anyone tell me what I am doing wrong?

2
Can you expand upon "my app screws up?". What are you able to observe is happening?Darryl H. Thomas
Also, based on your description, it sounds like you have a lower bound (countdown from 60), so presumably, you'll want to invalidate the timer at some point. While this isn't your problem, you'll want to refer to the timer later on to invalidate it. Maybe you're doing this later in your code, but since you used a local var initially, I figured it's worth mentioning to save you a headache later.Darryl H. Thomas
Basically the label doesn't update.Roosh
Can you show more code? For instance we have no way of knowing the declaration of updateLabelDisplay, does it take any arguments? If it does, say it takes one argument (of type NSTimer), you need @selector(updateLabelDisplay:). This is probably best anyway because then updateLabelDisplay will see the local declaration of the NSTime instance and can take the responsibility of sending it the -invalidate message when time is up. The code I posted below might help out.SK9

2 Answers

11
votes

Ok, well for starters, check this out if you haven't already: Official Apple Docs about Using Timers

Based on your description, you probably want code that looks something like this. I've made some assumptions regarding behavior, but you can suit to taste.

This example assumes that you want to hold on to a reference to the timer so that you could pause it or something. If this is not the case, you could modify the handleTimerTick method so that it takes an NSTimer* as an argument and use this for invalidating the timer once it has expired.

@interface MyController : UIViewController
{
  UILabel * theLabel;

  @private
  NSTimer * countdownTimer;
  NSUInteger remainingTicks;
}

@property (nonatomic, retain) IBOutlet UILabel * theLabel;

-(IBAction)doCountdown: (id)sender;

-(void)handleTimerTick;

-(void)updateLabel;

@end

@implementation MyController
@synthesize theLabel;

// { your own lifecycle code here.... }

-(IBAction)doCountdown: (id)sender
{
  if (countdownTimer)
    return;


  remainingTicks = 60;
  [self updateLabel];

  countdownTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector(handleTimerTick) userInfo: nil repeats: YES];
}

-(void)handleTimerTick
{
  remainingTicks--;
  [self updateLabel];

  if (remainingTicks <= 0) {
    [countdownTimer invalidate];
    countdownTimer = nil;
  }
}

-(void)updateLabel
{
  theLabel.text = [[NSNumber numberWithUnsignedInt: remainingTicks] stringValue];
}


@end
1
votes

It may be a little late to post a second answer to this question but I've been looking for a good place to post my own solution to this problem. In case it is of use to anyone here it is. It fires 8 times but of course this can be customised as you please. The timer deallocates itself when time is up.

I like this approach because it keeps the counter integrated with the timer.

To create an instance call something like:

SpecialKTimer *timer = [[SpecialKTimer alloc] initWithTimeInterval:0.1 
                                                         andTarget:myObject
                                                       andSelector:@selector(methodInMyObjectForTimer)];

Anyway, here are the header and method files.

//Header

#import <Foundation/Foundation.h>

@interface SpecialKTimer : NSObject {

    @private

    NSTimer *timer;

    id target;
    SEL selector;

    unsigned int counter;

}

- (id)initWithTimeInterval:(NSTimeInterval)seconds
                 andTarget:(id)t
               andSelector:(SEL)s;
- (void)dealloc;

@end

//Implementation

#import "SpecialKTimer.h"

@interface SpecialKTimer()

- (void)resetCounter;
- (void)incrementCounter;
- (void)targetMethod;

@end

@implementation SpecialKTimer

- (id)initWithTimeInterval:(NSTimeInterval)seconds
                 andTarget:(id)t
               andSelector:(SEL)s {

    if ( self == [super init] ) {

        [self resetCounter];

        target = t;
        selector = s;

        timer = [NSTimer scheduledTimerWithTimeInterval:seconds
                                                 target:self
                                               selector:@selector(targetMethod)
                                               userInfo:nil
                                                repeats:YES];

    }

    return self;

}

- (void)resetCounter {

    counter = 0;

}

- (void)incrementCounter {

    counter++;

}

- (void)targetMethod {

    if ( counter < 8 ) {

        IMP methodPointer = [target methodForSelector:selector];
        methodPointer(target, selector);

        [self incrementCounter];

    }

    else {

        [timer invalidate];
        [self release];

    }

}

- (void)dealloc {

    [super dealloc];

}

@end