3

My interface sometimes has buttons around its periphery. Areas without buttons accept gestures.

GestureRecognizers are added to the container view, in viewDidLoad. Here’s how the tapGR is set up:

UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playerReceived_Tap:)];
[tapGR setDelegate:self];
[self.view addGestureRecognizer:tapGR];

In order to prevent the gesture recognizers from intercepting button taps, I implemented shouldReceiveTouch to return YES only if the view touched is not a button:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gr 
   shouldReceiveTouch:(UITouch *)touch {

    // Get the topmost view that contains the point where the gesture started.
    // (Buttons are topmost, so if they were touched, they will be returned as viewTouched.)
    CGPoint pointPressed = [touch locationInView:self.view];
    UIView *viewTouched = [self.view hitTest:pointPressed withEvent:nil];

    // If that topmost view is a button, the GR should not take this touch.
    if ([viewTouched isKindOfClass:[UIButton class]])
        return NO;

    return YES;

}

This works fine most of the time, but there are a few buttons that are unresponsive. When these buttons are tapped, hitTest returns the container view, not the button, so shouldReceiveTouch returns YES and the gestureRecognizer commandeers the event.

To debug, I ran some tests...

The following tests confirmed that the button was a sub-subview of the container view, that it was enabled, and that both button and the subview were userInteractionEnabled:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gr 
   shouldReceiveTouch:(UITouch *)touch {

// Test that hierarchy is as expected: containerView > vTop_land > btnSkipFwd_land.
for (UIView *subview in self.view.subviews) {
    if ([subview isEqual:self.playComposer.vTop_land])
        printf("\nViewTopLand is a subview."); // this prints
}
for (UIView *subview in self.playComposer.vTop_land.subviews) {
    if ([subview isEqual:self.playComposer.btnSkipFwd_land])
        printf("\nBtnSkipFwd is a subview."); // this prints
}

// Test that problem button is enabled.
printf(“\nbtnSkipFwd enabled? %d", self.playComposer.btnSkipFwd_land.enabled); // prints 1

// Test that all views in hierarchy are interaction-enabled.
printf("\nvTopLand interactionenabled? %d", self.playComposer.vTop_land.userInteractionEnabled); // prints 1
printf(“\nbtnSkipFwd interactionenabled? %d", self.playComposer.btnSkipFwd_land.userInteractionEnabled); // prints 1

// etc

}

The following test confirms that the point pressed is actually within the button’s frame.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gr 
   shouldReceiveTouch:(UITouch *)touch {

CGPoint pointPressed = [touch locationInView:self.view];
CGRect rectSkpFwd = self.playComposer.btnSkipFwd_land.frame;

// Get the pointPressed relative to the button's frame.
CGPoint pointRelSkpFwd = CGPointMake(pointPressed.x - rectSkpFwd.origin.x, pointPressed.y - rectSkpFwd.origin.y);
printf("\nIs relative point inside skipfwd? %d.", [self.playComposer.btnSkipFwd_land pointInside:pointRelSkpFwd withEvent:nil]); // prints 1

// etc

}

So why is hitTest returning the container view rather than this button?

SOLUTION: The one thing I wasn't testing was that the intermediate view, vTop_land, was framed properly. It looked OK because it had an image that extended across the screen -- past the bounds of its frame (I didn't know this was possible). The frame was set to portrait width, rather than landscape width, so buttons on the far right were out of zone.

Wienke
  • 3,723
  • 27
  • 40

1 Answers1

2

Hit test is not reliable in most cases, and it is generally not advisable to use it along with gestureRecognizers.

Why dont you setExclusiveTouch:YES for each button, and this should make sure that the buttons are always chosen.

Legolas
  • 12,145
  • 12
  • 79
  • 132
  • I like this idea. It sounds more efficient than what I'm doing. At present the button is still not responding due to some other problem, though... – Wienke Apr 12 '13 at 00:40
  • I've marked this as the accepted answer because the suggestion is so good. I added the actual solution to the bottom of the question. – Wienke Apr 12 '13 at 15:24