21

I have a subclass of UIView in which I've overridden hitTest:withEvent: as follows:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Event = %@", event);
    return self;
}

For each touch in the view, I am seeing three calls to hitTest:withEvent:. These three calls are made before touch up. The output is as follows:

2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.554 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4304.54 touches: {(
)}

Based on the timestamps and addresses, it appears as if a single UITouchesEvent object is being used and its timestamp isn't properly set until the third call. Can anyone explain why hitTest:withEvent: gets called three times like this? I'm not looking for a workaround. I just want to understand what's going on.

James Huddleston
  • 8,410
  • 5
  • 34
  • 39
  • have you checked the stack trace in the debugger? i'd suspect different parts of the machinery are inquiring as to who the touch has hit – bshirley Jul 01 '11 at 16:54
  • 1
    @bshirley It looks like `_UIApplicationHandleEvent` is doing all the asking, twice through `[UIWindow _hitTestToPoint:pathIndex:forEvent:]` and once on its own. – James Huddleston Jul 01 '11 at 17:20
  • 1
    Did you ever have any luck figuring out why this happens or how to avoid it? I'm trying to do some logging for touches on a view but still want to let the touches through the view to it's superview. – Sahil Mar 09 '12 at 21:40
  • I've got no extra information. Sorry. – James Huddleston Mar 10 '12 at 19:10

4 Answers4

8

I had the same problem and was able to solve it with this code. Even though pointInside and hitTest get called 3 times, touchesBegan (or touchesEnded) of the UIView that was touched only gets called once.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{   
    if (event.type == UIEventTypeTouches)
        NSLog(@"%@", self);
}


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if ([self pointInside:point withEvent:event])
        return self;

    return [super hitTest:point withEvent:event];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (CGRectContainsPoint([self bounds], point))
    {
        if (event.type == UIEventTypeTouches)
        {           
            return YES;
        }
    }

    return NO;
}
RoadJunkie
  • 81
  • 1
  • 2
4

Yes, it’s normal. The system may tweak the point being hit tested between the calls. Since hitTest should be a pure function with no side-effects, this should be fine.

Refer to Apple Mailing List: Re: -hitTest:withEvent: called twice?

Frank
  • 624
  • 9
  • 27
4

Do you have more than one subview?

From the docs:

This method traverses the view hierarchy by sending the pointInside:withEvent: message to each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.

crgt
  • 1,482
  • 1
  • 14
  • 17
  • The view does not have any subviews. If I add subviews to it, it still gets three `hitTest:withEvent:` messages. Adding or removing sibling views doesn't change the result either. – James Huddleston Jul 12 '11 at 14:49
  • Maybe check here, this post may help clarify what's going on: http://stackoverflow.com/questions/4961386/event-handling-for-ios-how-hittestwithevent-and-pointinsidewithevent-are-re – crgt Jul 12 '11 at 23:55
-1

You should check to see if the subtype and type property are all the same. those 3 events do make sense since there's event that needs to be triggered in order for the OS to understand the nature of the touch event.

For example, swipe, pinch and tap all start with the same touch event. My guess is that the first two are fired 1 to register the tap event and the second to test for tap event to "move". the second is called not long afterwards probably to either cancel the pinching/zooming/whatever.

Bottom line, the documentations talks about 3 different type of events: touch, motion and remote events. UIEvent Class Reference

Pier-Olivier Thibault
  • 3,907
  • 2
  • 33
  • 33