withEvent:方法使用则hitTest上它的父的帧之外的子视图捕获的触摸:(Capturing

2019-06-17 14:38发布

我的问题:我有一个上海华EditView ,占用基本整个应用程序框架,以及一个子视图MenuView这会占用只有底部〜20%,然后MenuView包含自己的子视图ButtonView这实际上驻留之外MenuView的界限(东西像这样: ButtonView.frame.origin.y = -100 )。

(注: EditView有没有一部分的其他子视图MenuView的视图层次,但可能会影响到答案。)

你可能已经知道的问题:当ButtonView是的范围内MenuView (或者更具体地说,当我触摸不到MenuView的界限), ButtonView响应触摸事件。 当我接触的都是外面MenuView的界限(但仍然在ButtonView的界限),是由没有接收到触摸事件ButtonView

例:

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

图:

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

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

目标:据我了解,重写hitTest:withEvent:可以解决这个问题,但我完全不明白怎么样。 在我的情况下,应hitTest:withEvent:中被覆盖EditView (我“主人”上海华)? 还是应该在它被覆盖MenuView ,未接收触摸按钮的直接上海华? 或者,我想这个错误?

如果这需要一个详尽的解释,一个良好的网上资源将是有益的 - 除了苹果的UIView的文档,还没有明确向我。

谢谢!

Answer 1:

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

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

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



Answer 2:

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

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

图:

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

因为(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];
}

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

我希望帮助别人谁后打了这个问题。 :)



Answer 3:

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


Answer 4:

我会做的是既具有ButtonView和MenuView通过把它们都在一个容器中,其框架完全符合他们俩在视图层次结构的同一级别存在。 这样,裁剪项目的互动区域不会的,因为它被忽略的上海华盈的边界。



Answer 5:

如果有人需要它,这里是迅速替代

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
}


Answer 6:

如果你有你的父视图内许多其他子视图然后可能大部分的其他互动观点不会,如果您使用上述解决方案的工作,在这种情况下,你可以使用这样的事情(在斯威夫特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)
    }
}


Answer 7:

下面放置代码行到您的视图层次:

- (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”关于“自定义标注:按钮无法点击的问题”对于更多的澄清,它在我的博客进行了解释。

我希望帮助你...!



文章来源: Capturing touches on a subview outside the frame of its superview using hitTest:withEvent: