Collection View Default Gestures

By default a collection view listens for a single tap gesture to allow a cell to be highlighted and selected. This means that if you want to add a custom tap gesture you should avoid a single tap and maybe use a long-press or a double tap gesture. This is what a wanted to do while I was exploring a bug with popovers. My intention was to add a custom double tap gesture that would show the popover.

Unfortunately at the time (this was still prior to the GM release of iOS 7) the Collection View Programming Guide for iOS contained some confusing advice about how to work with the default gesture recognizers. There was some wrong sample code showing how to configure the default tap gesture recognizers so that they would only trigger if the custom recognizer failed. The bug report I filed with Apple was closed without comment but I notice as I write this that the documentation has now been corrected. Anyway for the avoidance of doubt here are the details on adding a double tap gesture recognizer for a collection view.

Adding The Tap Gesture Recognizer

There is really nothing special about adding a tap gesture to a collection view. It is worth noting that even though what I am interested in is a double tap on a collection view cell the gesture recognizer is actually added to the collection view. As we will see shortly it is trivial to determine if the tap happened inside a cell.

If you are using Interface Builder you can drag and drop a tap gesture recognizer onto the collection view. Use the attributes inspector to configure it to recognize two taps. The most important step is to ensure that “Canceled in View” and “Delayed Begin” are set. The first is set by default but the second is not. Setting “Delayed Begin” is what delays the single tap gesture from being recognised so the double tap can be detected.

You also need to ensure that you correctly connect all of the outlets:

  • the delegate of the gesture recognizer should be the collection view controller.
  • the tap gesture must be attached to the collection view by setting the gestureRecognizers outlet of the view
  • the gesture action should be connected to our custom method declared in the collection view controller. In this case I have defined a method named doubleTappedCell:

If you prefer to create your gestures with code you can do the following in the viewDidLoad method of the collection view controller:

- (void)viewDidLoad {
  [super viewDidLoad];
  UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self 
                                      action:@selector(doubleTappedCell:)];
  doubleTap.numberOfTapsRequired = 2;
  doubleTap.delaysTouchesBegan = YES;
  [self.collectionView addGestureRecognizer:doubleTap];
}

Detecting Taps In A Cell

If everything is connected correctly a double tap anywhere inside the collection view will fire our method. On reflection I probably made a mistake in naming the method doubleTappedCell as we still need to do some work to determine if the double tap actually happened on a cell and not somewhere else in the view. It turns out that the collection view makes that easy. Here is the method:

- (IBAction)doubleTappedCell:(id)sender
{
  CGPoint tappedPoint = [sender locationInView:self.collectionView];
  NSIndexPath *tappedCellPath = [self.collectionView indexPathForItemAtPoint:tappedPoint];

  if (tappedCellPath)
  {
    UYLSimpleCell *cell = (UYLSimpleCell *)[self.collectionView cellForItemAtIndexPath:tappedCellPath];
    [self.collectionView selectItemAtIndexPath:tappedCellPath
                                      animated:YES 
                                scrollPosition:UICollectionViewScrollPositionNone];
    
    if (self.uylPopoverController == nil)
    {
      UYLViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:UYLStoryboardViewControllerID];
      self.uylPopoverController = [[UIPopoverController alloc] initWithContentViewController:viewController];
      self.uylPopoverController.delegate = self;
    }
    
    [self.uylPopoverController presentPopoverFromRect:cell.frame
                                            inView:self.collectionView
                          permittedArrowDirections:UIPopoverArrowDirectionAny
                                          animated:YES];
  }
}

The first line of this method uses the sending gesture recognizer to get the location in the collection view of the user’s tap. The next line asks the collection view to return the index path of the cell, if any, that is at that location. If the indexPathForItemAtPoint: method returns an index path we know the cell where the user tapped. If we get back nil for the index path then the tap did not happen on a cell so we ignore the gesture. The remainder of the method deals with creating and presenting a popover from the tapped cell.

Wrapping Up

It turns out that adding a double tap gesture recognizer to a collection view is pretty simple, especially now that Apple has corrected the documentation. You can find the original example Collection Xcode project archived in my GitHub CodeExamples repository.