24
votes

Task

Add a single tap gesture to UICollectionView, do not get in the way of cell selection.

I want some other taps on the no-cell part of the collectionView.

Code

Using XCode8, Swift 3.

override func viewDidLoad() {
    ...
    collectionView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    print(indexPath)
}

func tap(sender: UITapGestureRecognizer){
    print("tapped")
}

Result

Yeah, it gets in the way now. When you tap on cell, it logs "tapped".

Analysis

  • I check the hitTest return value of the collectionView and the cell. Both returned the tapped cell, which means they form a responder chain of Cell -> CollectionView
  • no gestures on the cell
  • 3 gestures on collectionView, no one seems to work with the cell select
    • UIScrollViewDelayedTouchesBeganGestureRecognizer
    • UIScrollViewPanGestureRecognizer
    • UITapGestureRecognizer
  • callStack, seems cell selection has a different stack trace with gesture's target-action pattern.
  • double tap gesture works along with cell selection.

Question

Couldn't find more trace. Any ideas on how cell selection is implemented or to achieve this task?

2

2 Answers

41
votes

Whenever you want to add a gesture recognizer, but not steal the touches from the target view, you should set UIGestureRecognizer.cancelsTouchesInView for your gestureRecognizer instance to false.

17
votes

Instead of trying to force didSelectItem you can just get the indexPath and/or cell this way:

func tap(sender: UITapGestureRecognizer){

    if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
        let cell = self.collectionView?.cellForItem(at: indexPath)
        print("you can do something with the cell or index path here")
    } else {
        print("collection view was tapped")
    }
}