繁体   English   中英

如何防止 UINavigationBar 阻止对视图的触摸

[英]How to prevent UINavigationBar from blocking touches on view

我有一个UIView ,它部分卡在处于全屏模式的UIViewController上的UINavigationBar下方。 UINavigationBar阻止该视图对其覆盖的部分的触摸。 我希望能够为所述视图解锁这些触摸并让它们通过。 我已经将 UINavigationBar 子类化为以下内容:

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

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

...我已经用数字 399 标记了有问题的视图。是否可以在没有指向它的指针的情况下通过该视图的触摸(即就像我在上面标记它的方式一样)? 我对如何使用 hittest 方法(或者甚至可能)进行这项工作有点困惑。

这是一个不需要设置您想要在下面启用的特定视图的版本。 相反,它允许任何触摸通过,除非该触摸发生在UIControl或带有UIGestureRecognizer的视图中。

import UIKit

/// Passes through all touch events to views behind it, except when the
/// touch occurs in a contained UIControl or view with a gesture
/// recognizer attached
final class PassThroughNavigationBar: UINavigationBar {

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard nestedInteractiveViews(in: self, contain: point) else { return false }
        return super.point(inside: point, with: event)
    }

    private func nestedInteractiveViews(in view: UIView, contain point: CGPoint) -> Bool {

        if view.isPotentiallyInteractive, view.bounds.contains(convert(point, to: view)) {
            return true
        }

        for subview in view.subviews {
            if nestedInteractiveViews(in: subview, contain: point) {
                return true
            }
        }

        return false
    }
}

fileprivate extension UIView {
    var isPotentiallyInteractive: Bool {
        guard isUserInteractionEnabled else { return false }
        return (isControl || doesContainGestureRecognizer)
    }

    var isControl: Bool {
        return self is UIControl
    }

    var doesContainGestureRecognizer: Bool {
        return !(gestureRecognizers?.isEmpty ?? true)
    }
}

子类化 UINavigationBar 并覆盖- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event以便它为您要接收触摸的视图所在的矩形返回NO ,否则返回YES

例如:

UINavigationBar 子类.h:

@property (nonatomic, strong) NSMutableArray *viewsToIgnoreTouchesFor; 

UINavigationBar 子类.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;
}

在您拥有导航栏后面视图的全屏 viewController 中,将这些行添加到viewDidLoad

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

请注意:这不会将触摸发送到导航栏,这意味着如果您在导航栏上的按钮后面添加一个视图,导航栏上的按钮将不会收到触摸。

迅速:

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)
}

有关pointInside:withEvent:更多信息,请参阅文档pointInside:withEvent:

此外,如果pointInside:withEvent:无法按照您的意愿工作,则可能值得尝试使用上面的hitTest:withEvent:代码。

我修改了 Tricky 的解决方案以使用 SwiftUI 作为Extension 非常适合解决这个问题。 将此代码添加到代码库后,所有视图都将能够捕获屏幕顶部的点击。

也将此更改发布到我的博客

import UIKit

/// Passes through all touch events to views behind it, except when the
/// touch occurs in a contained UIControl or view with a gesture
/// recognizer attached
extension UINavigationBar {
    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard nestedInteractiveViews(in: self, contain: point) else { return false }
        return super.point(inside: point, with: event)
    }

    private func nestedInteractiveViews(in view: UIView, contain point: CGPoint) -> Bool {
        if view.isPotentiallyInteractive, view.bounds.contains(convert(point, to: view)) {
            return true
        }

        for subview in view.subviews {
            if nestedInteractiveViews(in: subview, contain: point) {
                return true
            }
        }

        return false
    }
}

private extension UIView {
    var isPotentiallyInteractive: Bool {
        guard isUserInteractionEnabled else { return false }
        return (isControl || doesContainGestureRecognizer)
    }

    var isControl: Bool {
        return self is UIControl
    }

    var doesContainGestureRecognizer: Bool {
        return !(gestureRecognizers?.isEmpty ?? true)
    }
}

上述目标 C 答案的Swift解决方案。

class MyNavigationBar: UINavigationBar {

    var viewsToIgnoreTouchesFor:[UIView] = []

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var pointInside = super.point(inside: point, with: event)

        for each in viewsToIgnoreTouchesFor {
            let convertedPoint = each.convert(point, from: self)
            if each.point(inside: convertedPoint, with: event) {
                pointInside = false
                break
            }
        }
        return pointInside
    }
}

现在从 viewDidLoad 方法或代码中的任何适用位置设置要在导航栏下方捕获其触摸的视图,如下所示

if let navBar = self.navigationController?.navigationBar as? MyNavigationBar {
    navBar.viewsToIgnoreTouchesFor = [btnTest]
}

来自@Tricky 的优秀回答!

但我最近注意到它在装有最新 iOS 的 iPad 上不起作用。 事实证明,自从引入键盘以来,iPad 上的任何导航栏都内置了手势识别器。

所以我做了一个简单的调整,让它再次工作:

/// Passes through all touch events to views behind it, except when the
/// touch occurs in a contained UIControl or view with a gesture
/// recognizer attached
final class PassThroughNavigationBar: UINavigationBar {

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard nestedInteractiveViews(in: self, contain: point) else { return false }
        return super.point(inside: point, with: event)
    }

    private func nestedInteractiveViews(in view: UIView, contain point: CGPoint) -> Bool {

        if view.isPotentiallyInteractive, view.bounds.contains(convert(point, to: view)) {
            return true
        }

        for subview in view.subviews {
            if nestedInteractiveViews(in: subview, contain: point) {
                return true
            }
        }

        return false
    }
}

fileprivate extension UIView {
    
    var isPotentiallyInteractive: Bool {
        guard isUserInteractionEnabled else { return false }
        return (isControl || doesContainGestureRecognizer) && !(self is PassThroughNavigationBar)
    }

    var isControl: Bool {
        return self is UIControl
    }

    var doesContainGestureRecognizer: Bool {
        return !(gestureRecognizers?.isEmpty ?? true)
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM