Search code examples
iosiphonecocoa-touchuiview

How to prevent UINavigationBar from blocking touches on view


I have a UIView that is partially stuck underneath a UINavigationBar on a UIViewController that's in full screen mode. The UINavigationBar blocks the touches of this view for the portion that it's covering it. I'd like to be able to unblock these touches for said view and have them go through. I've subclassed UINavigationBar with the following:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *view = [super hitTest:point withEvent:event];

    if (view.tag == 399)
    {
        return view;
    }
    else
    {
        return nil;
    }
}

...where I've tagged the view in question with the number 399. Is it possible to pass through the touches for this view without having a pointer to it (i.e. like how I've tagged it above)? Am a bit confused on how to make this work with the hittest method (or if it's even possible).


Solution

  • Subclass UINavigationBar and override- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event such that it returns NO for the rect where the view you want to receive touches is and YES otherwise.

    For example:

    UINavigationBar subclass .h:

    @property (nonatomic, strong) NSMutableArray *viewsToIgnoreTouchesFor; 
    

    UINavigationBar subclass .m:

    - (NSMutableArray *)viewsToIgnoreTouchesFor
    {
        if (!_viewsToIgnoreTouchesFor) {
            _viewsToIgnoreTouchesFor = [@[] mutableCopy];
        }
        return _viewsToIgnoreTouchesFor;
    }
    
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
        BOOL pointInSide = [super pointInside:point withEvent:event];
        for (UIView *view in self.viewsToIgnoreTouchesFor) {
    
            CGPoint convertedPoint = [view convertPoint:point fromView:self];
            if ([view pointInside:convertedPoint withEvent:event]) {
                pointInSide = NO;
                break;
            }
        }
    
        return pointInSide;
    }
    

    In your fullscreen viewController where you have the view behind the navBar add these lines to viewDidLoad

    UINavigationBarSubclass *navBar = 
    (UINavigationBarSubclass*)self.navigationController.navigationBar;
    [navBar.viewsToIgnoreTouchesFor addObject:self.buttonBehindBar];
    

    Please note: This will not send touches to the navigationBar, meaning if you add a view which is behind buttons on the navBar the buttons on the navBar will not receive touches.

    Swift:

    var viewsToIgnore = [UIView]()
    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    
        let ignore = viewsToIgnore.first {
            let converted = $0.convert(point, from: self)
            return $0.point(inside: converted, with: event)
        }
        return ignore == nil && super.point(inside: point, with: event)
    }
    

    See the documentation for more info on pointInside:withEvent:

    Also if pointInside:withEvent: does not work how you want, it might be worth trying the code above in hitTest:withEvent: instead.