17
votes

I have a bunch of UIViews like in the image below. The red/pink (semi-transparent) view is on top of the others.

  • Red has a UISwipeGestureRecognizer.
  • Green has as a UITapGestureRecognizer.
  • Blue has no recognizer.

enter image description here

A tap on the visible (bottom-left) part of Green trigger its recognizer.

A tap on the hidden parts of Green does not trigger its recognizer (Red blocks it).

That's the problem: I want Green to trigger. How can I do this?

In practice, the views may be in any order, any number and be subviews of each others etc. But the problem is the same:

How can I reliably find the uppermost view that can handle the gesture (tap or swipe)?


I tried with the code below. It neatly traverses all views, but it fails since it cannot know if the event is part of a swipe or a tap. So the method always returns the red view. If I remove the swipe-recognizer from Red, the code works correctly.

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event 
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self)
    {
        if (self.hasASwipeRecognizer)
            return self;  // What if this was a tap?
        if (self.hasATapRecognizer)
            return self; 
        else
            return nil;
    }
    else
         return hitView;
 }
3

3 Answers

17
votes

An alternative to adding the gesture recognizer to these views would be to add the gesture recognizers to the parent view and handle the use cases appropriately using the delegate method gestureRecognizer:shouldReceiveTouch: method.

Identify whether the particular recognizer should receive the touch and return YES. For example, if the gesture recognizer passed is a swipe recognizer then check if the touch point is within the green view and return YES. Return NO otherwise.

If there are similar gesture recognizers then I suggest that you keep a reference and verify against it.

Usage

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    CGPoint pointInView = [touch locationInView:gestureRecognizer.view];

    if ( [gestureRecognizer isMemberOfClass:[UITapGestureRecognizer class]] 
        && CGRectContainsPoint(self.blueView.frame, pointInView) ) {
        return YES;
    } 

    if ( [gestureRecognizer isMemberOfClass:[UISwipeGestureRecognizer class]] 
        && CGRectContainsPoint(self.greenView.frame, pointInView) ) {
        return YES;
    }

    return NO;
}
3
votes

One possible solution would be to add a tap gesture recognizer to the top red view and then whenever you get the tap, check whether the tap point intersects with the green view. If so, forward the tap to that view. If not, ignore the tap.

3
votes

My solutions is:

-(void)handleGesture:(UIGestureRecognizer*)gestureRecognizer {
    CGPoint touchPoint = [tapGestureRecognizer locationInView:viewUnderTest];
    if ([viewUnderTest pointInside:touchPoint withEvent:nil]) {
        NSLog(@"Hit done in view under test");
    }
}