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

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

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

你可能已经知道这个问题了:当ButtonViewMenuView的范围内(或者更确切地说,当我的触摸在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中重写,没有接收的button的直接超视图接触? 还是我错误地想这个?

如果这需要一个漫长的解释,一个好的在线资源将是有益的 – 除了苹果公司的UIView文件,这些文件没有向我明确。

谢谢!

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

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

我希望这可以帮助任何人尝试使用这个解决scheme来处理更复杂的用例。

好吧,我做了一些挖掘和testing,以下是hitTest:withEvent工作方式 – 至less在高层次上。 形象这个场景:

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

图:

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

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

但是,如果你实现了hitTest:withEvent: in(M),那么在应用程序中的任何地方点击都会被发送到(M)(或者最less知道它们)。 在这种情况下,您可以编写代码来处理触摸,并返回应该接触的对象。

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

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

我希望能帮助其他任何稍后碰到这个问题的人。 🙂

在Swift3中

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

我所要做的就是将ButtonView和MenuView放置在视图层次结构的同一级别,方法是将它们放在两个框架完全适合的容器中。 通过这种方式,裁剪的项目的交互区域将不会被忽略,因为它是超视图的边界。

如果有人需要,这里是快速的select

 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 } 

如果你在父视图中有许多其他的子视图,那么如果你使用上面的解决scheme,大概其他的交互视图可能不会工作,在这种情况下,你可以使用像这样的东西(在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) } } 

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

 - (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”关于“自定义标注:button不可点击的问题”。

我希望能帮助你… !!!