How to recognize oneTap/doubleTap at moment?
This is easiest to do without gesture recognizers. Then you can control the delay. The code below is a variation of Apple's original documentation that I use in one of my projects. I have blog post that talks about it as well.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if (touch.tapCount == 2) {
//This will cancel the singleTap action
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if (touch.tapCount == 1) {
//if they tapped within the coin then place the single tap action to fire after a delay of 0.3
if (CGRectContainsPoint(coin.frame,[touch locationInView:self.view])){
//this is the single tap action being set on a delay
[self performSelector:@selector(onFlip) withObject:nil afterDelay:0.3];
}else{
//I change the background image here
}
} else if (touch.tapCount == 2) {
//this is the double tap action
[theCoin changeCoin:coin];
}
}
Here is a simple custom gesture recognizers for double taps where you can specify the maximum allowed time between taps. This is based on @Walters answer.
PbDoubleTapGestureRecognizer.h :
@interface PbDoubleTapGestureRecognizer : UIGestureRecognizer
@property (nonatomic) NSTimeInterval maximumDoubleTapDuration;
@end
PbDoubleTapGestureRecognizer.m :
#import "PbDoubleTapGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
@interface PbDoubleTapGestureRecognizer ()
@property (nonatomic) int tapCount;
@property (nonatomic) NSTimeInterval startTimestamp;
@end
@implementation PbDoubleTapGestureRecognizer
- (id)initWithTarget:(id)target action:(SEL)action {
self = [super initWithTarget:target action:action];
if (self) {
_maximumDoubleTapDuration = 0.3f; // assign default value
}
return self;
}
-(void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)reset {
[super reset];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
self.tapCount = 0;
self.startTimestamp = 0.f;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if (touches.count != 1 ) {
self.state = UIGestureRecognizerStateFailed;
} else {
if (self.tapCount == 0) {
self.startTimestamp = event.timestamp;
[self performSelector:@selector(timeoutMethod) withObject:self afterDelay:self.maximumDoubleTapDuration];
}
self.tapCount++;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if (self.tapCount > 2) {
self.state = UIGestureRecognizerStateFailed;
} else if (self.tapCount == 2 && event.timestamp < self.startTimestamp + self.maximumDoubleTapDuration) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSLog(@"Recognized in %f", event.timestamp - self.startTimestamp);
self.state = UIGestureRecognizerStateRecognized;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateFailed;
}
- (void)timeoutMethod {
self.state = UIGestureRecognizerStateFailed;
}
@end
You can use it like this:
PbDoubleTapGestureRecognizer *doubleTapGr = [[PbDoubleTapGestureRecognizer alloc]initWithTarget:self action:@selector(_doubleTapAction)];
doubleTapGr.maximumDoubleTapDuration = 0.4;
[yourView addGestureRecognizer:doubleTapGr];
You can combine this with requireGestureRecognizerToFail:
to get the behavior that was asked for.
The easiest way to do this is to subclass UITapGestureRecognizer and not a general UIGestureRecognizer.
Like this:
#import <UIKit/UIGestureRecognizerSubclass.h>
#define UISHORT_TAP_MAX_DELAY 0.2
@interface UIShortTapGestureRecognizer : UITapGestureRecognizer
@end
And simply implement:
@implementation UIShortTapGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(UISHORT_TAP_MAX_DELAY * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
{
// Enough time has passed and the gesture was not recognized -> It has failed.
if (self.state != UIGestureRecognizerStateRecognized)
{
self.state = UIGestureRecognizerStateFailed;
}
});
}
@end
only thing you need to do is add extra line of code to use requireGestureRecognizerToFail
[singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
then whole code become to:
UITapGestureRecognizer *doubleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(beginComicTransitions:)] autorelease];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
doubleTapRecognizer.delegate = self;
UITapGestureRecognizer *singleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(bringMenu:)] autorelease];
singleTapRecognizer.numberOfTapsRequired = 1;
singleTapRecognizer.numberOfTouchesRequired = 1;
singleTapRecognizer.delegate = self;
[singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
here's requireGestureRecognizerToFail
means:
- if not recognized double tap, then single tap be recognized
- if recognized double tap, will not recognize single tap
swift version code is:
let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTapped:")
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
self.scrollView.addGestureRecognizer(doubleTap)
let singleTap = UITapGestureRecognizer(target: self, action: "singleTap:")
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
self.scrollView.addGestureRecognizer(singleTap)
singleTap.requireGestureRecognizerToFail(doubleTap)