19

I am struggling to get the behaviour I would like from the gesture recognisers, specifically cancelling certain gestures if others have fired.

I have a scrollView set to paging and multiple subviews in each page. I have added a touch gesture recogniser to scroll to the next or prev page if the user taps to the right or left of the page.

    // Add a gesture recogniser turn pages on a single tap at the edge of a page
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHandler:)];
    tapGesture.cancelsTouchesInView = NO;
    [self addGestureRecognizer:tapGesture];
    [tapGesture release];

and my gesture handler:

- (void) tapGestureHandler:(UIGestureRecognizer *) gestureRecognizer {
    const CGFloat kTapMargin = 180;

    // Get the position of the point tapped in the window co-ordinate system
    CGPoint tapPoint = [gestureRecognizer locationInView:nil];

    // If the tap point is to the left of the page then go back a page
    if (tapPoint.x > (self.frame.size.width - kTapMargin)) [self scrollRectToVisible:pageViewRightFrame animated:YES];

    // If the tap point is to the right of the page then go forward a page
    else if (tapPoint.x < kTapMargin) [self scrollRectToVisible:pageViewLeftFrame animated:YES];
}

All works well, except where I have a subview on the page that has buttons in it. I want to be able to ignore the tap to turn the page if the user touches a button on the subView and I can't figure out how to do this.

Cheers

Dave

Magic Bullet Dave
  • 9,006
  • 10
  • 51
  • 81

4 Answers4

20

The solution that worked the best for me in the end was to use the hitTest to determine if there were any buttons underneath the location of the tap gesture. If there are then just ignore the rest of the gesture code.

Seems to work well. Would like to know if there are any gotchas with what I have done.

- (void) tapGestureHandler:(UIGestureRecognizer *) gestureRecognizer {
    const CGFloat kTapMargin = 180;

    // Get the position of the point tapped in the window co-ordinate system
    CGPoint tapPoint = [gestureRecognizer locationInView:nil];

    // If there are no buttons beneath this tap then move to the next page if near the page edge
    UIView *viewAtBottomOfHeirachy = [self.window hitTest:tapPoint withEvent:nil];
    if (![viewAtBottomOfHeirachy isKindOfClass:[UIButton class]]) {

        // If the tap point is to the left of the page then go back a page
        if (tapPoint.x > (self.bounds.size.width - kTapMargin)) [self scrollRectToVisible:pageViewRightFrame animated:YES];

        // If the tap point is to the right of the page then go forward a page
        else if (tapPoint.x < kTapMargin) [self scrollRectToVisible:pageViewLeftFrame animated:YES];
    }   
}
Magic Bullet Dave
  • 9,006
  • 10
  • 51
  • 81
  • It's the best way I could imagine to solve the problem. Great! – Ruben Marin May 09 '11 at 11:35
  • This seems to be the answer I looking for, but where do I implement this tapGestureHandler? How does it trigger? Cheers. – f0rz Jun 30 '11 at 08:53
  • @f0rz The first block of code in my question sets up a tap gesture recognizer in my own class, which is a subclass of UIView. Essentially you create a UIGestureRecognizer object and use the addGestureRecognizer: method of UIView to add it. – Magic Bullet Dave Sep 14 '11 at 15:39
  • @MagicBulletDave: I had a similar problem and using your solution I cancelled the gesture if there was a UIButton at bottom of the hierarchy but I wanted to know how do I let my UIButton get the press event which it is not getting. – Amogh Talpallikar Jan 20 '12 at 05:37
  • Hi Amogh, you need to make sure the cancelsTouchesInView property of your gesture recogizer is set to NO. – Magic Bullet Dave Jan 20 '12 at 08:15
15

Apple documentation shows the answer:

- (void)viewDidLoad {
      [super viewDidLoad];
      // Add the delegate to the tap gesture recognizer
      self.tapGestureRecognizer.delegate = self;
}

// Implement the UIGestureRecognizerDelegate method
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:    (UITouch *)touch {
      // Determine if the touch is inside the custom subview
     if ([touch view] == self.customSubview){
         // If it is, prevent all of the delegate's gesture recognizers
         // from receiving the touch
        return NO;
     }
     return YES;
}

Of course in this case customSubview would be subview on the page that has buttons in it (or even the buttons on it)

M Penades
  • 1,572
  • 13
  • 24
4

Swift 3

Set UITapGestureRecognizer

let tap = UITapGestureRecognizer(target: self, action: #selector(Class.didTap))
tap.delegate = self

UITapGestureRecognizer delegate method:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view != buttonThatShouldCancelTapGesture
}
Chris Tsitsaris
  • 590
  • 10
  • 12
0

You can subclass UIView and overwrite the -touchesBegan selector, or you can play with the opaque property of the subviews to make them "invisible" to the touches (if view.opaque = NO, the view ignores touch events).

Ruben Marin
  • 1,639
  • 14
  • 24
  • Hi Ruben, thanks for the suggestions. I am not quite sure they will help in this instance. Essentially I want to trap the button press in the subView and then cancel touches in the superView. The UIGestureRecognizer will get all the touches first as I understand it and I am not sure if a UIButton uses a gesture recognizer or home cooked touches to do its thing. – Magic Bullet Dave May 03 '11 at 18:41
  • Ok I solved a similar situation by adding a long press gesture recognizer to the top view: when the user long press the page, it gets access to the subviews (and all the buttons you want to have there). Use UILongPressGestureRecognizer – Ruben Marin May 04 '11 at 08:03
  • Cheers Ruben, will give it a go and let you know how I get on. – Magic Bullet Dave May 04 '11 at 10:35
  • 1
    Hi Ruben, sorry I just could not get this to work and it felt like I was adding more and more code. I have come up with a solution (see my answer) and wont accept this one yet until I get some feedback as to its validity. Thanks for all the help, Dave. – Magic Bullet Dave May 09 '11 at 10:29
  • With my last answer I wanted to propose a solution that probably doesn't fit your requirements. My point was that you could put a UILongPressGestureRecognizer in the upper view that, when detected, would animate that view to disappear and let the user touch all of the buttons underneath. That's not what you really needed! Sorry for the inconvenience – Ruben Marin May 09 '11 at 11:34
  • Hey no problem at all, thanks for trying to help! It is much appreciated especially when you've been banging your head against a brick wall for days. Take care, Dave. – Magic Bullet Dave May 09 '11 at 12:03