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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11770743/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 19:32:25  来源:igfitidea点击:

Capturing touches on a subview outside the frame of its superview using hitTest:withEvent:

objective-ciosuiview-hierarchy

提问by toblerpwn

My problem:I have a superview EditViewthat takes up basically the entire application frame, and a subview MenuViewwhich takes up only the bottom ~20%, and then MenuViewcontains its own subview ButtonViewwhich actually resides outside of MenuView's bounds (something like this: ButtonView.frame.origin.y = -100).

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

(note: EditViewhas other subviews that are not part of MenuView's view hierarchy, but may affect the answer.)

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

You probably already know the issue: when ButtonViewis within the bounds of MenuView(or, more specifically, when my touches are within MenuView's bounds), ButtonViewresponds to touch events. When my touches are outside of MenuView's bounds (but still within ButtonView's bounds), no touch event is received by ButtonView.

您可能已经知道这个问题:何时ButtonView在 的范围内MenuView(或更具体地说,当我的触摸在MenuView的范围内时),ButtonView响应触摸事件。当我的触摸超出MenuView's 的范围(但仍在ButtonView's 的范围内)时,ButtonView.

Example:

例子:

  • (E) is EditView, the parent of all views
  • (M) is MenuView, a subview of EditView
  • (B) is ButtonView, a subview of MenuView
  • (E) is EditView,所有视图的父级
  • (M) is MenuView,EditView的一个子视图
  • (B) is ButtonView,MenuView的一个子视图

Diagram:

图表:

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

Because (B) is outside (M)'s frame, a tap in the (B) region will never be sent to (M) - in fact, (M) never analyzes the touch in this case, and the touch is sent to the next object in the hierarchy.

因为 (B) 在 (M) 的框架之外,所以 (B) 区域中的敲击永远不会被发送到 (M) - 事实上,在这种情况下, (M) 从不分析触摸,而触摸被发送到层次结构中的下一个对象。

Goal:I gather that overriding hitTest:withEvent:can solve this problem, but I don't understand exactly how. In my case, should hitTest:withEvent:be overridden in EditView(my 'master' superview)? Or should it be overridden in MenuView, the direct superview of the button that is not receiving touches? Or am I thinking about this incorrectly?

目标:我认为覆盖hitTest:withEvent:可以解决这个问题,但我不明白具体是如何解决的。在我的情况下,应该hitTest:withEvent:EditView(我的“主”超级视图)中被覆盖吗?还是应该在MenuView未接收触摸的按钮的直接超级视图中覆盖它?还是我想错了?

If this requires a lengthy explanation, a good online resource would be helpful - except Apple's UIView docs, which have not made it clear to me.

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

Thanks!

谢谢!

回答by Noam

I have modified the accepted answer's code to be more generic - it handles the cases where the view does clip subviews to its bounds, may be hidden, and more importantly : if the subviews are complex view hierarchies, the correct subview will be returned.

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

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

斯威夫特 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
}

I hope this helps anyone trying to use this solution for more complex use cases.

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

回答by toblerpwn

Ok, I did some digging and testing, here's how hitTest:withEventworks - at least at a high level. Image this scenario:

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

  • (E) is EditView, the parent of all views
  • (M) is MenuView, a subview of EditView
  • (B) is ButtonView, a subview of MenuView
  • (E) 是 EditView,所有视图的父视图
  • (M) 是 MenuView,是 EditView 的子视图
  • (B) 是 ButtonView,MenuView 的一个子视图

Diagram:

图表:

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

Because (B) is outside (M)'s frame, a tap in the (B) region will never be sent to (M) - in fact, (M) never analyzes the touch in this case, and the touch is sent to the next object in the hierarchy.

因为 (B) 在 (M) 的框架之外,所以 (B) 区域中的敲击永远不会被发送到 (M) - 事实上,在这种情况下, (M) 从不分析触摸,而触摸被发送到层次结构中的下一个对象。

However, if you implement hitTest:withEvent:in (M), taps anywhere in in the application will be sent to (M) (or it least it knows about them). You can write code to handle the touch in that case and return the object that should receive the touch.

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

More specifically: the goal of hitTest:withEvent:is to return the object that should receive the hit. So, in (M) you might write code like this:

更具体地说:目标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];
}

I am still very new to this method and this problem, so if there are more efficient or correct ways to write the code, please comment.

我对这个方法和这个问题还很陌生,所以如果有更有效或更正确的方法来编写代码,请评论。

I hope that helps anyone else who hits this question later. :)

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

回答by duan

In Swift 5

在斯威夫特 5

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
}

回答by mamackenzie

What I would do is have both the ButtonView and MenuView exist at the same level in the view hierarchy by placing them both in a container whose frame completely fits both of them. This way the interactive region of the clipped item will not be ignored because of it's superview's boundaries.

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

回答by paras gupta

If you have many other subviews inside your parent view then probably most of other interactive views would not work if you use above solutions, in that case you can use something like this(In Swift 3.2):

如果您的父视图中有许多其他子视图,那么如果您使用上述解决方案,那么大多数其他交互式视图可能无法正常工作,在这种情况下,您可以使用以下内容(在 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)
    }
}

回答by Sandip Patel - SM

Place below lines of code into your view hierarchy:

将以下代码行放入您的视图层次结构中:

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

For the more clarification, it was explained in my blog: "goaheadwithiphonetech" regarding "Custom callout : Button is not clickable issue".

为了获得更多说明,我的博客中对此进行了解释:“goaheadwithiphonetech”关于“自定义标注:按钮不是可点击的问题”。

I hope that helps you...!!!

希望能帮到你。。。!!!

回答by Sonny

If anyone needs it, here is the swift alternative

如果有人需要它,这里是快速的替代方案

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
}