Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIGestureRecognizer blocks subview for handling touch events

I'm trying to figure out how this is done the right way. I've tried to depict the situation: enter image description here

I'm adding a UITableView as a subview of a UIView. The UIView responds to a tap- and pinchGestureRecognizer, but when doing so, the tableview stops reacting to those two gestures (it still reacts to swipes).

I've made it work with the following code, but it's obviously not a nice solution and I'm sure there is a better way. This is put in the UIView (the superview):

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if([super hitTest:point withEvent:event] == self) {
        for (id gesture in self.gestureRecognizers) {
            [gesture setEnabled:YES];
        }
        return self;
    }
    for (id gesture in self.gestureRecognizers) {
        [gesture setEnabled:NO];
    }
    return [self.subviews lastObject];
}
like image 551
andershqst Avatar asked Mar 07 '11 17:03

andershqst


4 Answers

I had a very similar problem and found my solution in this SO question. In summary, set yourself as the delegate for your UIGestureRecognizer and then check the targeted view before allowing your recognizer to process the touch. The relevant delegate method is:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch
like image 72
Justin Avatar answered Nov 14 '22 21:11

Justin


The blocking of touch events to subviews is the default behaviour. You can change this behaviour:

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];
like image 27
Clive Paterson Avatar answered Nov 14 '22 22:11

Clive Paterson


I was displaying a dropdown subview that had its own tableview. As a result, the touch.view would sometimes return classes like UITableViewCell. I had to step through the superclass(es) to ensure it was the subclass I thought it was:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    UIView *view = touch.view;
    while (view.class != UIView.class) {
        // Check if superclass is of type dropdown
        if (view.class == dropDown.class) { // dropDown is an ivar; replace with your own
            NSLog(@"Is of type dropdown; returning NO");
            return NO;
        } else {
            view = view.superview;
        }
    }

    return YES;
}
like image 5
joslinm Avatar answered Nov 14 '22 22:11

joslinm


Building on @Pin Shih Wang answer. We ignore all taps other than those on the view containing the tap gesture recognizer. All taps are forwarded to the view hierarchy as normal as we've set tapGestureRecognizer.cancelsTouchesInView = false. Here is the code in Swift3/4:

func ensureBackgroundTapDismissesKeyboard() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    tapGestureRecognizer.cancelsTouchesInView = false
    self.view.addGestureRecognizer(tapGestureRecognizer)
}

@objc func handleTap(recognizer: UIGestureRecognizer) {
    let location = recognizer.location(in: self.view)
    let hitTestView = self.view.hitTest(location, with: UIEvent())
    if hitTestView?.gestureRecognizers?.contains(recognizer) == .some(true) {
        // I dismiss the keyboard on a tap on the scroll view
        // REPLACE with own logic
        self.view.endEditing(true)
    }
}
like image 5
Nick Ager Avatar answered Nov 14 '22 21:11

Nick Ager