akbsteam
akbsteam

Reputation: 253

Firing repeating events on Touch Down in iOS

I am working on an application where I want to call a method every few seconds whilst the user has their finger on a button, and stops on release.

At the moment I am setting off an NSOperation on the Touch Down event, which should then call an NSTimer to fire another NSOperation 2 seconds later.

However only the first "runOperation" is happening; the ones from the timer aren't.

- (IBAction)buttonPressed:(id)sender
{
    [self doStuff];
}

- (void)runOperation {
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(doStuff)
                                                                          object:nil];

    [queue addOperation:operation];
    [operation release];
}

- (void)doStuff {
    /* stuff goes here */

    [self performSelectorOnMainThread:@selector(setTimer) withObject:nil waitUntilDone:YES];
}

- (void)setTimer
{
    timer = [[NSTimer timerWithTimeInterval:2.f target:self selector:@selector(runOperation) userInfo:nil repeats:NO] retain];
}

- (IBAction)finishTakingPictures:(id)sender {
    [timer invalidate];
    timer = nil;
}

Upvotes: 2

Views: 2208

Answers (2)

NJones
NJones

Reputation: 27147

XJones (no relation) is right about the scheduled timer, and about the ridiculous amount of indirection. But I personally do like the NSTimer for this user interaction model. Here is how I would implement it.

_buttonTimer is an instance variable,
button is an IBOutlet to the button in question,
touchDown: is connected to the button's TouchDown event.

Adding the button as an outlet removes the need to have a BOOL to track the button state since the button knows it's state.

Then add these methods to your UIViewController subclass.

-(void)helloMe{
    if (self.button.state == UIControlStateNormal){ 
        [_buttonTimer invalidate];
        _buttonTimer = nil
    }
    else {
        NSLog(@"Hello me");
        // Do stuff here
    }
}

- (IBAction)touchDown:(id)sender {
    _buttonTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(helloMe) userInfo:nil repeats:YES];
    [_buttonTimer fire]; // If desired
}

Now run you will see "Hello me" in the console upon the push of the button and every two seconds until you release the button.

Upvotes: 2

XJones
XJones

Reputation: 21967

Man, hope this helps you out, don't know why I spent so much time on this... :)

If you use NSTimer:timerWithTimeInterval:... you need to add the timer to a run loop using NSRunLoop:addTimer:forMode. That's why your timer isn't firing. If you use NSTimer:scheduledTimerWithTimeInerval:... it will be added to the current run loop.

A few thoughts on your implementation.

  1. You have lots of indirection. You can set it directly in doStuff and get rid of the setTimer method.

  2. There's no reason to set the timer on the main thread, you can set it on any thread.

  3. You don't really need a timer at all, I'll suggest an alternate implementation below.

ALTERNATE IMPLEMENTATION:

  1. Add a BOOL property e.g. @property (nonatomic) BOOL isFinished;

  2. When the user presses the button, set isFinished = NO; and start your first operation.

  3. When the operation completes, if isFinished == NO start another operation. You can add a delay by using performSelector:afterDelay: or using dispatch_after()

  4. When the user stops pressing the button, set isFinished = YES

This alternate implementation assumes you want to start the next operation after the first is complete. If you want the operation to happen after a fixed interval independent of whether an operation is already processing change your timer to repeat and when the timer fires check isFinished == YES to invalidate the timer.

Upvotes: 1

Related Questions