Search code examples
iosuiscrollviewuigesturerecognizerhittestuievent

iOS - UIScrollView hitTest doesn't contain touches


I have a UIScrollView with UIViews inside it. The UIScrollView has two-finger scroll enabled. The UIViews each have a panGestureRecognizer. I want the following functionality:

If two-touch pan --> scroll.

If single touch pan && touch on a UIView --> fire UIView's panGestureRecognizer.

I want to do this by overwriting UIScrollView's hitTest. If the number of touches is greater than 1, return the UIScrollView to possibly scroll. If the number of touches is 1, return the normal hitTest result to possibly fire UIView's panGestureRecognizer. But my UIScrollView's hitTest code never has any touches! (Although I successfully two-finger scroll, the hitTest doesn't have any touches.)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    NSSet *touches = [event touchesForView:self];
    NSLog(@"%@", touches);
    if ([touches count] > 1)
    {
        return self;
    }
    UIView *usualView = [super hitTest:point withEvent:event];
    return usualView;
}

Solution

  • HitTest is a low level overridable method for handling touches, or better said, for detection of the destination of the touches. You cannot know number of touches here - event parameter is useless. Instead, you get called twice for each touch, meaning that for double touch you get called 4 times. It is not suitable for detection of a number of touches or gestures inside, just for the destination of the touches.

    Default implementation looks something like:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self _isAnimatedUserInteractionEnabled]) {
            return nil;
        } else {
            for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
                UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
                if (hitView) {
                    return hitView;
                }
            }
            return self;
        }
    }