使用hitTest:withEvent捕获超级视图框架外部的子视图:
我的问题:我有一个基本上占用了整个应用程序框架的一个EditView
视图EditView
和一个子视图MenuView
,它只占用底部ButtonView
%,然后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
中重写,没有接收的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不可点击的问题”。
我希望能帮助你… !!!