views:

149

answers:

2

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?

A: 

Ok, well for starters, check this out if you haven't already: http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/Timers/Articles/usingTimers.html

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
Darryl H. Thomas
It's worth noting that an even better way of doing this would be to use a time interval to update the label based on the elapsed time instead of relying on a tick counter. Due to the nature of runloops, you are not guaranteed any amount of precision as to when the timer fires. But the above is good enough for sample code.
Darryl H. Thomas
Thank you so much! Just one question, the doCountdown would be connected to my UIButton correct?
Rushil
That is correct. doCountdown: is the action to wire to your button.
Darryl H. Thomas
A: 

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
SpecialK
Calling `-dealloc` on self is bad. See http://stackoverflow.com/questions/3819603/suicide-objective-c-objects-calling-their-own-dealloc-methods-on-themselves.
SpecialK