iOS
UITableView
UITapGestureRecognizer
Swift
didSelectRowAtIndexPath

UITapGestureRecognizer breaks UITableView didSelectRowAtIndexPath

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A UITapGestureRecognizer can interfere with tableView(_:didSelectRowAt:) because both the table view and the gesture recognizer are trying to handle the same touch sequence. The most common cause is the recognizer canceling touches before the table view finishes its built-in selection handling. The fix is usually to configure the recognizer so it cooperates with the table view instead of competing with it.

Why the Conflict Happens

UITableView already has its own internal gesture handling for row selection, scrolling, highlighting, and accessory interactions. If you add a tap recognizer to the table view or a parent view, that recognizer can consume or cancel the touch.

The property that causes the biggest surprise is:

  • 'cancelsTouchesInView'

By default, it is true, which means the recognizer can prevent the table view from receiving the full touch sequence it expects.

The First Fix: Do Not Cancel Table Touches

If your tap recognizer is only there to detect background taps or dismiss a keyboard, start by disabling touch cancellation.

swift
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)

That alone is often enough to restore didSelectRowAt while still allowing the tap recognizer to fire.

Use the Gesture Delegate to Ignore Table Rows

If the recognizer should not react to taps on table cells at all, filter them out in the delegate.

swift
1import UIKit
2
3class ViewController: UIViewController, UIGestureRecognizerDelegate, UITableViewDelegate {
4    @IBOutlet weak var tableView: UITableView!
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8
9        let tap = UITapGestureRecognizer(target: self, action: #selector(backgroundTapped))
10        tap.cancelsTouchesInView = false
11        tap.delegate = self
12        view.addGestureRecognizer(tap)
13    }
14
15    @objc func backgroundTapped() {
16        view.endEditing(true)
17    }
18
19    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
20        return !(touch.view?.isDescendant(of: tableView) ?? false)
21    }
22
23    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
24        print("selected row", indexPath.row)
25    }
26}

This is often the cleanest solution when the recognizer is conceptually for the background, not for the table.

Sometimes the Recognizer Belongs Somewhere Else

Another design fix is to attach the tap recognizer to a more specific view instead of the entire root view. For example, if the recognizer exists only to dismiss the keyboard when the user taps outside a search field, you may not need it to observe touches over the table at all.

Broad recognizers attached at the top of the view hierarchy are more likely to conflict with controls below them.

Simultaneous Recognition Is Another Option

In more advanced setups, you can allow simultaneous recognition.

swift
1func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
2                       shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
3    return true
4}

This is useful in some combinations, but it is not always the right first fix. If the tap recognizer simply should not handle cell taps, explicit filtering is usually clearer than broad simultaneous recognition.

Debug the Intent, Not Just the Symptom

A helpful question is: why is the tap recognizer there?

Common intentions include:

  • dismiss keyboard on background tap
  • close a popup when touching outside it
  • detect taps in empty table space

Each of those suggests a slightly different placement or delegate rule. If you identify the real intent, the fix is usually simpler than trying to force both handlers to process every touch.

Common Pitfalls

A common mistake is leaving cancelsTouchesInView at its default value and expecting the table view to continue selecting rows normally.

Another mistake is attaching the recognizer to the root view when the desired behavior applies only to a much smaller area.

People also often use simultaneous recognition without first deciding whether the recognizer should receive those touches at all.

Finally, remember that didSelectRowAt is only one of the table's built-in interactions. A badly placed recognizer can also interfere with scrolling and accessory taps.

Summary

  • 'UITapGestureRecognizer can break didSelectRowAt because it competes with the table view for the same touches'
  • The most common immediate fix is tap.cancelsTouchesInView = false
  • If the recognizer should ignore table rows, filter those touches with a gesture delegate
  • Attach the recognizer as narrowly as possible instead of to the broadest parent view by default
  • Use simultaneous recognition only when both handlers genuinely need the same touch sequence
  • Fix the recognizer's intent and scope, not just the visible row-selection symptom

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.