Whenever you have a modal element that outside touches should cancel a few things need to happen:

  • Touches inside the modal element should behave normally
  • Outside touches should not trigger touch events on tappable elements
  • Outside touches should trigger a handler to close the modal

In my case I was swiping a UITableViewCell and revealing a controls view that should close if any other UITableViewCell is tapped.

hitTest:withEvent – The easiest method

Touch events bubble down from the root view rather than up from the target view which makes this a bit easier. On whichever view needs to capture the touch events, we need to override hitTest:withEvent: to return nil and call a function whenever a modal is open and a view other than the modal is tapped. I overrode this method in my UITableView’s superview.

A naive implementation looks like this:

Gotcha

The only problem with this method is that hitTest:withEvent: is called 3 times per tap so our flow looks something like:

  1. Handle first tap correctly
  2. See that modal is no longer open and pass 2nd and 3rd tap through

To correct this we need to make sure is_modal_open continues to return YES until after that 3rd hitTest gets called and make sure the handler only gets triggered once. Full method below.