With a similar problem to this question, I am trying to add a double tap gesture recognizer to my UICollectionView
instance.
I need to prevent the default single tap from calling the UICollectionViewDelegate
method collectionView:didSelectItemAtIndexPath:
.
In order to achieve this I implement the code straight from Apple's Collection View Programming Guide (Listing 4-2):
UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; NSArray* recognizers = [self.collectionView gestureRecognizers]; // Make the default gesture recognizer wait until the custom one fails. for (UIGestureRecognizer* aRecognizer in recognizers) { if ([aRecognizer isKindOfClass:[UITapGestureRecognizer class]]) [aRecognizer requireGestureRecognizerToFail:tapGesture]; } // Now add the gesture recognizer to the collection view. tapGesture.numberOfTapsRequired = 2; [self.collectionView addGestureRecognizer:tapGesture];
This code does not work as expected: tapGesture
fires on a double tap but the default single tap is not prevented and the delegate's didSelect...
method is still called.
Stepping through in the debugger reveals that the if condition, [aRecognizer isKindOfClass:[UITapGestureRecognizer class]]
, never evaluates to true and so the failure-requirement on the new tapGesture
is not being established.
Running this debugger command each time through the for-loop:
po (void)NSLog(@"%@",(NSString *)NSStringFromClass([aRecognizer class]))
reveals that the default gesture recognizers are (indeed) not UITapGestureRecognizer
instances.
Instead they are private classes UIScrollViewDelayedTouchesBeganGestureRecognizer
and UIScrollViewPanGestureRecognizer
.
First, I can't use these explicitly without breaking the rules about Private API. Second, attaching to the UIScrollViewDelayedTouchesBeganGestureRecognizer
via requireGestureRecognizerToFail:
doesn't appear to provide the desired behaviour anyway — i.e. the delegate's didSelect...
is still called.
How can I work with UICollectionView
's default gesture recognizers to add a double tap to the collection view and prevent the default single tap from also firing the delegate's collectionView:didSelectItemAtIndexPath:
method?
Thanks in advance!
My solution was to not implement collectionView:didSelectItemAtIndexPath but to implement two gesture recognizers.
self.doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(processDoubleTap:)];
[_doubleTapGesture setNumberOfTapsRequired:2];
[_doubleTapGesture setNumberOfTouchesRequired:1];
[self.view addGestureRecognizer:_doubleTapGesture];
self.singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(processSingleTap:)];
[_singleTapGesture setNumberOfTapsRequired:1];
[_singleTapGesture setNumberOfTouchesRequired:1];
[_singleTapGesture requireGestureRecognizerToFail:_doubleTapGesture];
[self.view addGestureRecognizer:_singleTapGesture];
This way I can handle single and double taps. The only gotcha I can see is that the cell is selected on doubleTaps but if this bothers you can you handle it in your two selectors.
I use the following to register a UITapGestureRecognizer:
UITapGestureRecognizer* singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)];
singleTapGesture.delaysTouchesBegan = YES;
singleTapGesture.numberOfTapsRequired = 1; // number of taps required
singleTapGesture.numberOfTouchesRequired = 1; // number of finger touches required
[self.collectionView addGestureRecognizer:singleTapGesture];
By setting delaysTouchesBegan
to YES
the custom gesture recognizer gets priority over the default collection view tap listeners by delaying the registering of other touch events. Alternatively, you can set cancel touch recognition altogether by setting the cancelsTouchesInView
to YES
.
The gesture is than handled by the following function:
- (void)handleSingleTapGesture:(UITapGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
CGPoint location = [sender locationInView:self.collectionsView];
NSIndexPath *indexPath = [self.collectionsView indexPathForItemAtPoint:location];
if (indexPath) {
NSLog(@"Cell view was tapped.");
UICollectionViewCell *cell = [self.collectionsView cellForItemAtIndexPath:indexPath];
// Do something.
}
}
else{
// Handle other UIGestureRecognizerState's
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With