Tap region issues with UINavigationController in V

2019-09-17 15:17发布

问题:

I have an iOS app in which I add a UINavigationController as a child view controller to the main screen. It's arranged vertically, so that the nav controller takes up a bottom part of the main view's height and is full-width. There is a button (actually a custom view with a tap recognizer) that sits right above the nav that is used to hide and show it; when the nav is hidden, the button is at the bottom of the main screen, and when the nav is shown the button is right above the top of the nav bar.

The issue is that the navigation bar appears to intercept touches on the bottom 10 points or so of the button. Why is this?

The code to add the child looks like this:

UIViewController *root = [[[UIViewController alloc]init]autorelease]; //some VC
UINavigationController *nav = [[[UINavigationController alloc]initWithRootViewController:root]autorelease];
[self addChildViewController:nav];
CGRect rect = self.view.frame;
CGFloat height = 500;
nav.view.frame = CGRectMake(0, rect.size.height - height, rect.size.width, height);
[self.view addSubview:nav.view];
[nav didMoveToParentViewController:self];

//Now the button
UIView *view = [[[UIView alloc]initWithFrame:CGRectZero]autorelease];
view.backgroundColor = [UIColor greenColor];
[self.view addSubview:view];
view.frame = CGRectMake(0, rect.size.height - 500 - 40, rect.size.width, 40);
UITapGestureRecognizer *recog = [[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(onToggleSplit)]autorelease];
[view addGestureRecognizer:recog];

onToggleSplit animates the nav up and down. When it's up, taps on the bottom 10 points or so of the "button" do nothing. I tried overriding touchesBegan on my custom "button" view, and that doesn't even get called.

If I use a regular UIViewController (or a subclass thereof) for the child VC, the button responds as expected.

回答1:

This has been asked and answered here. Basically, controls on iOS have "slop zones" designed to allow sloppy touch accuracy on the part of the user. I'm going to file a bug about how this shouldn't reach outside the control's view controller's bounds.

Update: filed as radar 19504573.

Update 2: my radar has been marked as a duplicate of 8088542, which is open. Also, my case is somewhat simple (one view right on top of a navigation bar), so I was able to overcome the issue. I overrode hitTest:withEvent: and noticed that when a tap happens, it gets called twice. On the first, the touch coordinates are what you expect, and calling super returns the view you expect. The second has the same timestamp value on the event, but the y coordinate is higher (lower on the screen) and super returns the nav bar. So:

  • Subclass UIView and add a property viewToWatch.
  • In my main view controller's nib/storyboard, set the view class to my subview.
  • In my main view controller's viewDidLoad, grab the view, cast it, and set its viewToWatch to the view whose touch events are being filched.
  • In the custom view, define two private properties: NSTimeInterval lastTimestamp and BOOL isInView.
  • Override hitTest:withEvent:. If calling super yields viewToWatch, set the lastTimestamp to the event's stamp and isInView to YES. Otherwise, if super returns a nav bar, if the event's stamp matches and isInView is still YES, reset both properties and return viewToWatch.

It's working so far.