Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add double tap to UICollectionView; require single tap to fail

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!

like image 671
Carlton Gibson Avatar asked Jul 16 '13 08:07

Carlton Gibson


2 Answers

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.

like image 134
Paul Cezanne Avatar answered Sep 23 '22 05:09

Paul Cezanne


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
    }
}
like image 37
Szilto Avatar answered Sep 26 '22 05:09

Szilto