使用hitTest:withEvent捕获其超级视图框架外的子视图的触摸:

tob*_*pwn 91 objective-c ios uiview-hierarchy

我的问题:我有一个超级视图EditView,基本上占用整个应用程序框架,子视图MenuView只占用底部〜20%,然后MenuView包含它自己的子视图ButtonView,它实际上位于MenuView边界之外(类似这样:) ButtonView.frame.origin.y = -100.

(注意:EditView其他子视图不属于MenuView视图层次结构,但可能会影响答案.)

您可能已经知道了这个问题:何时ButtonViewMenuView(或者更具体地说,当我的触摸在MenuView范围内)时,ButtonView响应触摸事件.当我的触摸超出MenuView界限(但仍然在ButtonView界限内)时,不会收到任何触摸事件ButtonView.

例:

  • (E)是EditView所有观点的父母
  • (M)是MenuViewEditView的子视图
  • (B)是ButtonViewMenuView的子视图

图:

+------------------------------+
|E                             |
|                              |
|                              |
|                              |
|                              |
|+-----+                       |
||B    |                       |
|+-----+                       |
|+----------------------------+|
||M                           ||
||                            ||
|+----------------------------+|
+------------------------------+
Run Code Online (Sandbox Code Playgroud)

因为(B)在(M)的框架之外,(B)区域中的抽头将永远不会被发送到(M) - 实际上,(M)在这种情况下从不分析触摸,并且触摸被发送到层次结构中的下一个对象.

目标:我认为压倒一切hitTest:withEvent:可以解决这个问题,但我不明白究竟是怎么回事.在我的情况下,应该hitTest:withEvent:被覆盖EditView(我的'主'超级视图)?或者是否应该覆盖MenuView,没有接收触摸的按钮的直接超级视图?或者我错误地想到了这个?

如果这需要一个冗长的解释,一个好的在线资源会有所帮助 - 除了Apple的UIView文档,我还没有说清楚.

谢谢!

Noa*_*oam 140

我已经修改了接受的答案的代码更通用 - 它处理视图将子视图剪辑到其边界的情况,可能是隐藏的,更重要的是:如果子视图是复杂的视图层次结构,将返回正确的子视图.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    if (self.clipsToBounds) {
        return nil;
    }

    if (self.hidden) {
        return nil;
    }

    if (self.alpha == 0) {
        return nil;
    }

    for (UIView *subview in self.subviews.reverseObjectEnumerator) {
        CGPoint subPoint = [subview convertPoint:point fromView:self];
        UIView *result = [subview hitTest:subPoint withEvent:event];

        if (result) {
            return result;
        }
    }

    return nil;
}
Run Code Online (Sandbox Code Playgroud)

SWIFT 3

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

    if clipsToBounds || isHidden || alpha == 0 {
        return nil
    }

    for subview in subviews.reversed() {
        let subPoint = subview.convert(point, from: self)
        if let result = subview.hitTest(subPoint, with: event) {
            return result
        }
    }

    return nil
}
Run Code Online (Sandbox Code Playgroud)

我希望这有助于任何人尝试将此解决方案用于更复杂的用例.

  • 根据所需的用途,它应该返回[super hitTest:point withEvent:event]或nil.回归自我会使它接受一切. (3认同)
  • 我刚刚使用你的解决方案让UIButton捕获了触摸,它位于一个UIView内部,显然位于UICollectionView内部(很明显)是一个UICollectionView.我不得不在这三个类上继承UICollectionView和UICollectionViewCell以覆盖hitTest:withEvent :. 它就像魅力一样!谢谢 !! (2认同)
  • 以下是 Apple 关于同一技术的问答技术文档:https://developer.apple.com/library/ios/qa/qa2013/qa1812.html (2认同)

tob*_*pwn 33

好的,我做了一些挖掘和测试,这里的hitTest:withEvent工作原理 - 至少在高水平.想象这个场景:

  • (E)是EditView,是所有视图的父级
  • (M)是MenuView,EditView的子视图
  • (B)是ButtonView,是MenuView的子视图

图:

+------------------------------+
|E                             |
|                              |
|                              |
|                              |
|                              |
|+-----+                       |
||B    |                       |
|+-----+                       |
|+----------------------------+|
||M                           ||
||                            ||
|+----------------------------+|
+------------------------------+
Run Code Online (Sandbox Code Playgroud)

因为(B)在(M)的框架之外,(B)区域中的抽头将永远不会被发送到(M) - 实际上,(M)在这种情况下从不分析触摸,并且触摸被发送到层次结构中的下一个对象.

但是,如果hitTest:withEvent:在(M)中实现,应用程序中任何位置的点击都将被发送到(M)(或者它至少知道它们).在这种情况下,您可以编写代码来处理触摸并返回应该接收触摸的对象.

更具体地说:目标hitTest:withEvent:是返回应该接收命中的对象.所以,在(M)中你可以写这样的代码:

// need this to capture button taps since they are outside of self.frame
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{   
    for (UIView *subview in self.subviews) {
        if (CGRectContainsPoint(subview.frame, point)) {
            return subview;
        }
    }

    // use this to pass the 'touch' onward in case no subviews trigger the touch
    return [super hitTest:point withEvent:event];
}
Run Code Online (Sandbox Code Playgroud)

我仍然是这个方法和这个问题的新手,所以如果有更有效或正确的方法来编写代码,请评论.

我希望能帮助后来遇到这个问题的其他人.:)


dua*_*uan 24

在Swift 4中

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
    for member in subviews.reversed() {
        let subPoint = member.convert(point, from: self)
        guard let result = member.hitTest(subPoint, with: event) else { continue }
        return result
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

  • 仅当您在“行为不当”视图的**直接**超级视图上应用覆盖时,这才有效 (2认同)