When you touch the screen in iOS the first thing that needs to happen is to find the correct object to handle the tap or gesture. The way this is done is by calling

- (UIView *)hitTest:(CGPoint)p withEvent:(UIEvent *)event;

on the key window of the application (UIWindow is a subclass of UIView). This method will return the view that should handle gesture.

If you have set userInteractionEnabled to NO on this view then hitTest:withEvent: will always return nil. This will mean that neither the view nor any of its subviews will respond to touches. If user interaction is enabled then hitTest:withEvent: will check to see if it contains the point p. If it doesn’t contain the point p then it will return NO. This means means that none of the view’s subviews will be tested to see if they contain the point. This will cause a problem if you expect a view that goes over the edge of its superview’s bounds to respond to touches (it will only respond to touches that are also inside the superview’s bounds).

If hitTest:withEvent: determines that the point is within the view’s bounds then it will start calling hitTest:withEvent: on its subviews. If a subview returns a UIView then this is the UIView that the current hitTest:withEvent: will also return (it won’t check the other subviews). This results in the UIView that it returned being the one that handles the event. If none of the subviews returns a UIView then hitTest:withEvent: returns self, and the current view will handle the event.

The order that hitTest:withEvent: checks the subviews is important, as if there are two subviews that might both respond to a touch then only the first subview to have hitTest:withEvent: called on it will respond to that touch. The order that the subviews are called is the reverse of the order that you add them to the view i.e. the last subview added to the view is the first subview that will have hitTest:withEvent: called on it.

So hitTest:WithEvent: looks something like this:

hitTest:withEvent:
- (UIView *)hitTest:(CGPoint)p withEvent:(UIEvent *)event
{
if (![self userInteractionEnabled])
return nil;
// Check to see if we contain the point otherwise return nil
if (![self pointInside:p withEvent:event])
return nil;
NSArray *subviews = [self subviews];
NSUInteger subviewsCount = [subviews count];
for (int i = 1; i <= subviewsCount; ++i) {
UIView *subview = [subviews objectAtIndex:subviewsCount - i];
// Get the point in the subviews space.
CGPoint pointInSubview = [self convertPoint:p toView:subview];
UIView *result = [subview hitTest:pointInSubview withEvent:event];
if (result)
return result;
}
return self;
}

This is useful stuff to know, as sometimes there may be a situation where something is not responding, and you’d like it to. For instance, if I have some views as in this image:

Here the button is a subview of the red view. In this case if you clicked on the right hand side of the button (above the white background) it wouldn’t respond. To solve this you can either make it so pointInside:withEvent (on the red view) returns YES for the right-hand part of the button (if you do this the red view will handle the touch if the button doesn’t). Or you can make it so hitTest:withEvent: (on the red view) returns the button when the point is over the button, but not over the red view.

So you’d need to subclass the UIView for the red view. If you override the pointInside:withEven: it might look something like this:

hitTest:withEvent:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if ([myButton pointInside:[self convertPoint:point toView:myButton]
withEvent:event])
return YES;
return [super pointInside:point withEvent:event];
}

If instead you override the hitTest:withEvent: method then it may look something like this:

hitTest:withEvent:
- (UIView *)hitTest:(CGPoint)p withEvent:(UIEvent *)event
{
// Force it to hitTest the button before it checks the point
// is in its bounds.
UIView *result = [myButton hitTest:[self convertPoint:p toView:myButton]
withEvent:event];
if (result)
return result;
return [super hitTest:p withEvent:event];
}

This will work well if the button is only over superviews. However if the button is over a sibling view, or some other path in the view hierarchy the other path in the view hierarchy may be hit tested first and return a UIView; so the button’s superview doesn’t get hit tested, and the button won’t respond. In that case you may need to either change the order of some subviews (if you can), or override some other hit testing methods so the siblings don’t respond for the points under the button.