The Event Handling Guide for iOS: Event Delivery: The Responder Chain's "The Responder Chain Follows a Specific Delivery Path" section describes how touch events are passed first to the view that was touched, then up through all of its superviews, then to the window, and finally to the application itself.
A simplified representation of your project's view hierarchy would be:
mainViewController's Root View
| mainViewController's Container View (has Tap Gesture Recognizer)
| | UINavigationController's Root View
| | | contentViewController's View
| | | | UIButton ("Button")
| | | UINavigationController's Toolbar View
| | | | UIToolbarTextButton ("Item")
...so when you tap the button or the toolbar button, they receive the touch event before mainViewController's container view.
The reason why the button's event fires and the toolbar button's doesn't appears to be related to Event Handling Guide for iOS: Gesture Recognizers' "Interacting with Other User Interface Controls" section:
In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer.
That appears to explain why the UIButton
is able to preempt the tap gesture recognizer, but it doesn't say anything explicit about the toolbar button.
If you print out the view hierarchy you'll find that the toolbar button is represented using a UIToolbarButton
which is a private class that inherits directly from UIControl
. Based on our observations we would assume that UIToolbarButton
does not preempt gesture recognizers like the public UIControl
subclasses do. When I swizzled its touchesCancelled:withEvent:
method I found that it gets called after the tap gesture recognizer fires, which seems to be what you would expect based on Event Handling Guide for iOS: Gesture Recognizers's "Gesture Recognizers Get the First Opportunity to Recognize a Touch" section where they note:
...if the gesture recognizer recognizes a touch gesture, then the window never delivers the touch object to the view, and also cancels any touch objects it previously sent to the view that were part of that recognized sequence.
There are a few different ways you could modify this behavior and the one you picked would depend on your end goal. If you wanted to allow touches on the toolbar you could check if the UITouch
sent to the gesture recognizer's delegate's gestureRecognizer:shouldReceiveTouch:
was inside the toolbar's frame and return NO
if it was. Blocking touches to the UIButton
specifically would probably require subclassing, but if you wanted to block all touches to mainViewController's child view controllers you could add a transparent view over its container view.