UITapGestureRecognizer breaks UITableView didSelectRowAtIndexPath

Solution 1:

Ok, finally found it after some searching through gesture recognizer docs.

The solution was to implement UIGestureRecognizerDelegate and add the following:

#pragma mark UIGestureRecognizerDelegate methods
    
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
  if ([touch.view isDescendantOfView:autocompleteTableView]) {
            
    // Don't let selections of auto-complete entries fire the 
    // gesture recognizer
    return NO;
  }
        
  return YES;
}

That took care of it. Hopefully this will help others as well.

Solution 2:

The easiest way to solve this problem is to:

UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc] 
    initWithTarget:self action:@selector(tap:)];
[tapRec setCancelsTouchesInView:NO];

This lets the UIGestureRecognizer recognize the tap and also pass the touch to the next responder. An unintended consequence of this method is if you have a UITableViewCell on-screen that pushes another view controller. If the user taps the row to dismiss the keyboard, both the keyboard and the push will be recognized. I doubt this is what you intend, but this method is adequate for many situations.

Also, expanding on Robert's answer, if you have a pointer to the tableview in question, then you can directly compare its class instead of having to convert to a string and hope Apple doesn't change the nomenclature:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
     shouldReceiveTouch:(UITouch *)touch
{
    if([touch.view class] == tableview.class){
        return //YES/NO
    }

    return //YES/NO

}

Remember, you must also declare the UIGestureRecognizer to have a delegate with this code in it.

Solution 3:

Set cancelsTouchesInView of your recognizer to false. Otherwise, it "consumes" the touch for itself, and does not pass it on to the table view. That's why the selection event never happens.

for example in swift

let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)

Solution 4:

And for Swift (based on answer from @Jason):

class MyAwesomeClass: UIViewController, UIGestureRecognizerDelegate

private var tap: UITapGestureRecognizer!

override func viewDidLoad() {
   super.viewDidLoad()

   self.tap = UITapGestureRecognizer(target: self, action: "viewTapped:")
   self.tap.delegate = self
   self.view.addGestureRecognizer(self.tap)
}

// UIGestureRecognizerDelegate method
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view?.isDescendantOfView(self.tableView) == true {
        return false
    }
    return true
}

Solution 5:

I may have a better solution to add a tap gesture over a table view but allowing cell selection at the same time:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if gestureRecognizer is UITapGestureRecognizer {
        let location = touch.locationInView(tableView)
        return (tableView.indexPathForRowAtPoint(location) == nil)
    }
    return true
}

I just look for a cell at the point of the screen where the user is tapping. If no index path is found then I let the gesture receive the touch otherwise I cancel it. For me it works great.