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

我的问题:我有一个超级视图hitTest:withEvent:基本上占用了整个应用程序框架,并且子视图hitTest:withEvent:仅占用底部~20%,然后EditView包含其自己的子视图MenuView实际上位于MenuView的边界之外(类似这样的东西) :ButtonView)。

(注意:hitTest:withEvent:的其他子视图不属于hitTest:withEvent:的视图层次结构,但可能会影响答案。)

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

例:

  • (E)是hitTest:withEvent:,所有观点的父级
  • (M)是hitTest:withEvent:,EditView的子视图
  • (B)是hitTest:withEvent:,是MenuView的子视图

图:

+------------------------------+
|E                             |
|                              |
|                              |
|                              |
|                              |
|+-----+                       |
||B    |                       |
|+-----+                       |
|+----------------------------+|
||M                           ||
||                            ||
|+----------------------------+|
+------------------------------+

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

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

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

谢谢!

toblerpwn asked 2019-09-10T23:34:58Z
7个解决方案
132 votes

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

- (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;
}

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
}

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

Noam answered 2019-09-10T23:35:26Z
29 votes

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

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

图:

+------------------------------+
|E                             |
|                              |
|                              |
|                              |
|                              |
|+-----+                       |
||B    |                       |
|+-----+                       |
|+----------------------------+|
||M                           ||
||                            ||
|+----------------------------+|
+------------------------------+

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

但是,如果在(M)中实现hitTest:withEvent:,则应用程序中任何位置的点击都将被发送到(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];
}

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

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

toblerpwn answered 2019-09-10T23:36:44Z
20 votes

在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
}
duan answered 2019-09-10T23:37:09Z
2 votes

我要做的是让ButtonView和MenuView都存在于视图层次结构中的同一级别,方法是将它们放在一个容器中,该容器的框架完全适合它们。 这样,剪切项目的交互区域将不会被忽略,因为它是超视图的边界。

michael_mackenzie answered 2019-09-10T23:37:35Z
0 votes

如果有人需要它,这里是快速的选择

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
    if !self.clipsToBounds && !self.hidden && self.alpha > 0 {
        for subview in self.subviews.reverse() {
            let subPoint = subview.convertPoint(point, fromView:self);

            if let result = subview.hitTest(subPoint, withEvent:event) {
                return result;
            }
        }
    }

    return nil
}
Sonny answered 2019-09-10T23:38:00Z
0 votes

如果您在父视图中有许多其他子视图,那么如果您使用上述解决方案,则大多数其他交互式视图可能无效,在这种情况下,您可以使用类似这样的内容(在Swift 3.2中):

class BoundingSubviewsViewExtension: UIView {

    @IBOutlet var targetView: UIView!

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Convert the point to the target view's coordinate system.
        // The target view isn't necessarily the immediate subview
        let pointForTargetView: CGPoint? = targetView?.convert(point, from: self)
        if (targetView?.bounds.contains(pointForTargetView!))! {
            // The target view may have its view hierarchy,
            // so call its hitTest method to return the right hit-test view
            return targetView?.hitTest(pointForTargetView ?? CGPoint.zero, with: event)
        }
        return super.hitTest(point, with: event)
    }
}
paras gupta answered 2019-09-10T23:38:26Z
-1 votes

将以下代码行放在视图层次结构中:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* hitView = [super hitTest:point withEvent:event];
    if (hitView != nil)
    {
        [self.superview bringSubviewToFront:self];
    }
    return hitView;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
{
    CGRect rect = self.bounds;
    BOOL isInside = CGRectContainsPoint(rect, point);
    if(!isInside)
    {
        for (UIView *view in self.subviews)
        {
            isInside = CGRectContainsPoint(view.frame, point);
            if(isInside)
                break;
        }
    }
    return isInside;
}

为了更加澄清,我的博客中解释说:“goaheadwithiphonetech”关于“自定义标注:按钮不可点击的问题”。

我希望能帮助你...... !!!

Sandip Patel - SM answered 2019-09-10T23:39:05Z
translate from https://stackoverflow.com:/questions/11770743/capturing-touches-on-a-subview-outside-the-frame-of-its-superview-using-hittest