Stopping Tap Gesture from bubbling to child controls

The default behavior is for your tap or other gestures to bubble up to their child controls. This avoids the need to add a recognizer on all of your child controls. But this isn’t always the behavior you are looking for. For example imagine you create the below modal view. You want to add a tap gesture recognizer to the view itself so when your user taps the grey area it closes the modal. But you don’t want the gesture to be triggered when a tap is made on the content UIView of the modal.

modal-example

First adding the UITapGestureRecognizer

For our use case the first thing we need to do is add a UITapGestureRecognizer to the root UIView of the UIViewController. This will close our UIViewController when the grey area is tapped.

internal class ConfirmationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTapOffModal(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
}
}

Limiting the scope of the Gesture

So that the UITapGestureRecognizer isn’t triggered when a tap is made to the content UIView we simply need to add protocol method to restrict the tap to the view it is associated with.

extension ConfirmationViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
func handleTapOffModal(_ sender: UITapGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
}

Putting it all together

Below is the full code associated with the modal UIViewController. If you are not familiar with how to create a modal UIViewController I would recommend checking out Tim Sanders tutorial here.

import UIKit
internal class ConfirmationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTapOffModal(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
}
}
extension ConfirmationViewController {
@IBAction func cancel_click(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
@IBAction func confirm_click(_ sender: Any) {
print("Perform Confirmation action")
dismiss(animated: true, completion: nil)
}
}
extension ConfirmationViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
func handleTapOffModal(_ sender: UITapGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
}