I want to respond to double-taps on cells in a UICollectionView, and have a double-tap action cancel cell selection.
This is what I\'ve tried:
UITapGestu
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 don't see why you need the requireToFail. I use double-taps in a UICollectionView and it doesn't interfere with my single taps (used for selection).
I use the following:
UITapGestureRecognizer *doubleTapFolderGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(processDoubleTap:)];
[doubleTapFolderGesture setNumberOfTapsRequired:2];
[doubleTapFolderGesture setNumberOfTouchesRequired:1];
[self.view addGestureRecognizer:doubleTapFolderGesture];
Then, this:
- (void) processDoubleTap:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint point = [sender locationInView:collectionView];
NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:point];
if (indexPath)
{
NSLog(@"Image was double tapped");
}
else
{
DoSomeOtherStuffHereThatIsntRelated;
}
}
}
Seems to working fine -- the double tap is recognized and I can handle it as I wish (in this case I'm expanding the contents of a folder). But a single-tap will cause the tapped sell to be selected, which I haven't written any gesture recognition for.
IMPORTANT EDIT:
I am revisiting this question because I've seen that my original answer can be wrong in certain circumstances, and there is an apparent fix that seems to work.
The following line needs to be added:
doubleTapFolderGesture.delaysTouchesBegan = YES;
which eliminates interference with the single tap for cell selection. This provides a much more robust setup.
The requireGestureRecognizerToFail:
called on the default gesture recognisers does actually work (that is, their state goes to UIGestureRecognizerStateFailed if a double-tap is recognized).
But it seems UICollectionView's collectionView:didSelectItemAtIndexPath:
delegate callback doesn't take account of this, ie. it's still called when the default gesture recogniser fails.
So the answer/workaround is to make sure the delegate's collectionView:shouldSelectItemAtIndexPath:
and collectionView:shouldDeselectItemAtIndexPath:
implementations check the state of (one of?) the default gesture recognisers, thus:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UITapGestureRecognizer *defaultGestureRecogniser = [[self.collectionView gestureRecognizers] objectAtIndex:0];
return defaultGestureRecogniser.state != UIGestureRecognizerStateFailed;
}
There are a bunch of good solutions here but unfortunately they didn't work reliably for me (e.g. I could not get the double tap to trigger consistently possibly because I was also implemented didSelectItemAtIndexPath).
What worked for me was adding the (double)tap gesture recognizer to the collection view instead of the cell. In its action selector I would determine which cell was double tapped and do whatever I needed to do. Hopefully this helps someone:
- (void)viewDidLoad
{
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didDoubleTapCollectionView:)];
doubleTapGesture.numberOfTapsRequired = 2;
[self.collectionView addGestureRecognizer:doubleTapGesture];
}
- (void)didDoubleTapCollectionView:(UITapGestureRecognizer *)gesture {
CGPoint pointInCollectionView = [gesture locationInView:self.collectionView];
NSIndexPath *selectedIndexPath = [self.collectionView indexPathForItemAtPoint:pointInCollectionView];
UICollectionViewCell *selectedCell = [self.collectionView cellForItemAtIndexPath:selectedIndexPath];
// do something
}
For readers looking for a swift answer, this is a mix of @RegularExpression and @Edwin Iskandar answer.
In your controller holding the collectionView
add the following lines:
private var doubleTapGesture: UITapGestureRecognizer!
func setUpDoubleTap() {
doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTapCollectionView))
doubleTapGesture.numberOfTapsRequired = 2
collectionView.addGestureRecognizer(doubleTapGesture)
// This line delay the single touch message to the collection view.
// Simple touch will be sent only when the double tap recogniser is sure
// this is a simple tap.
// You can remove it if you don't mind having both a simple tap and double
// tap event.
doubleTapGesture.delaysTouchesBegan = true
}
@objc func didDoubleTapCollectionView() {
let pointInCollectionView = doubleTapGesture.location(in: collectionView)
if let selectedIndexPath = collectionView.indexPathForItem(at: pointInCollectionView) {
let selectedCell = collectionView.cellForItem(at: selectedIndexPath)
// Print double tapped cell's path
print(selectedCell)
}
}