虽然大多数苹果文档编写得很好,但我认为“Event Handling Guide for iOS”是一个例外。我很难清楚地了解那里所描述的内容。
该文件说,
在命中测试中,窗口在视图层次结构的最顶层视图上调用
hitTest:withEvent:
;此方法通过在视图层次结构中返回YES的每个视图上递归调用pointInside:withEvent:
继续进行,继续向下移动层次结构,直到找到触摸发生在其边界内的子视图。该视图成为热门测试视图。
因此,只有系统调用最顶层视图的hitTest:withEvent:
,调用所有子视图的pointInside:withEvent:
,如果从特定子视图返回的是,则调用该子视图的子类的pointInside:withEvent:
?
答案 0 :(得分:291)
我认为您对视图层次结构的子类化感到困惑。该文件所说的内容如下。假设您有此视图层次结构。按层次结构,我不是在讨论类层次结构,而是在视图层次结构中查看,如下所示:
+----------------------------+
|A |
|+--------+ +------------+ |
||B | |C | |
|| | |+----------+| |
|+--------+ ||D || |
| |+----------+| |
| +------------+ |
+----------------------------+
假设你把手指放在D
里面。这是将要发生的事情:
hitTest:withEvent:
在A
上调用,pointInside:withEvent:
是视图层次结构的最顶层视图。pointInside:withEvent:
。
A
上调用YES
,并返回pointInside:withEvent:
B
上调用NO
,并返回pointInside:withEvent:
C
上调用YES
,并返回pointInside:withEvent:
D
上调用YES
,并返回YES
A
的视图中,它会向下查看层次结构,以查看触摸发生的子视图。在这种情况下,来自C
,D
和D
,它将是D
。答案 1 :(得分:166)
这似乎是一个非常基本的问题。但我同意你的意见,文件不像其他文件那么清楚,所以这是我的答案。
UIResponder中hitTest:withEvent:
的实施执行以下操作:
pointInside:withEvent:
self
hitTest:withEvent:
返回nil
。故事的结尾。hitTest:withEvent:
条消息。
它从顶级子视图开始,并继续到其他视图,直到子视图
返回非nil
对象,或者所有子视图都会收到消息。nil
对象,则第一个hitTest:withEvent:
将返回该对象。故事的结尾。nil
对象,则第一个hitTest:withEvent:
会返回self
此过程以递归方式重复,因此通常最终会返回视图层次结构的叶视图。
但是,您可以覆盖hitTest:withEvent
以执行不同的操作。在许多情况下,覆盖pointInside:withEvent:
更简单,并且仍然提供了足够的选项来调整应用程序中的事件处理。
答案 2 :(得分:43)
我觉得这个Hit-Testing in iOS非常有用
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
编辑Swift 4:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
return super.hitTest(point, with: event)
}
guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
return nil
}
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return nil
}
答案 3 :(得分:21)
感谢您的回答,他们帮我解决了“叠加”观点的情况。
+----------------------------+
|A +--------+ |
| |B +------------------+ |
| | |C X | |
| | +------------------+ |
| | | |
| +--------+ |
| |
+----------------------------+
假设X
- 用户的触摸。 pointInside:withEvent:
上的B
会返回NO
,因此hitTest:withEvent:
会返回A
。我在UIView
上编写了类别,以便在您需要在最顶层可见视图上进行触摸时处理问题。
- (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1
if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0)
return nil;
// 2
UIView *hitView = self;
if (![self pointInside:point withEvent:event]) {
if (self.clipsToBounds) return nil;
else hitView = nil;
}
// 3
for (UIView *subview in [self.subviewsreverseObjectEnumerator]) {
CGPoint insideSubview = [self convertPoint:point toView:subview];
UIView *sview = [subview overlapHitTest:insideSubview withEvent:event];
if (sview) return sview;
}
// 4
return hitView;
}
userInteractionEnabled
设置为NO
的视图发送; self
内,则self
将被视为潜在结果。注意,[self.subviewsreverseObjectEnumerator]
需要遵循从最顶部到底部的视图层次结构。并检查clipsToBounds
以确保不测试屏蔽的子视图。
用法:
hitTest:withEvent:
醇>
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self overlapHitTest:point withEvent:event];
}
Official Apple's Guide也提供了一些很好的插图。
希望这有助于某人。
答案 4 :(得分:3)
它显示像这个片段!
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01)
{
return nil;
}
if (![self pointInside:point withEvent:event])
{
return nil;
}
__block UIView *hitView = self;
[self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CGPoint thePoint = [self convertPoint:point toView:obj];
UIView *theSubHitView = [obj hitTest:thePoint withEvent:event];
if (theSubHitView != nil)
{
hitView = theSubHitView;
*stop = YES;
}
}];
return hitView;
}
答案 5 :(得分:1)
@lion的片段就像一个魅力。我将它移植到swift 2.1并将其用作UIView的扩展。如果有人需要,我会在这里发帖。
extension UIView {
func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
// 1
if !self.userInteractionEnabled || self.hidden || self.alpha == 0 {
return nil
}
//2
var hitView: UIView? = self
if !self.pointInside(point, withEvent: event) {
if self.clipsToBounds {
return nil
} else {
hitView = nil
}
}
//3
for subview in self.subviews.reverse() {
let insideSubview = self.convertPoint(point, toView: subview)
if let sview = subview.overlapHitTest(insideSubview, withEvent: event) {
return sview
}
}
return hitView
}
}
要使用它,只需在你的uiview中覆盖hitTest:point:withEvent,如下所示:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let uiview = super.hitTest(point, withEvent: event)
print("hittest",uiview)
return overlapHitTest(point, withEvent: event)
}
答案 6 :(得分:0)
找到一个First Responder
First Responder
在这种情况下是最深的UIView
point()
方法,该方法返回true
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
func point(inside point: CGPoint, with event: UIEvent?) -> Bool
内部hitTest()
看起来像
func hitTest() -> View? {
if (isUserInteractionEnabled == false || isHidden == true || alpha == 0 || point() == false) { return nil }
for subview in subviews {
if subview.hitTest() != nil {
return subview
}
}
return nil
}
将触摸事件发送到First Responder
//UIApplication.shared.sendEvent()
//UIApplication, UIWindow
func sendEvent(_ event: UIEvent)
//UIResponder
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
我们来看一个例子
//UIApplication.shared.sendAction()
func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool
看看例子
class AppDelegate: UIResponder, UIApplicationDelegate {
@objc
func foo() {
//this method is called using Responder Chain
print("foo") //foo
}
}
class ViewController: UIViewController {
func send() {
UIApplication.shared.sendAction(#selector(AppDelegate.foo), to: nil, from: view1, for: nil)
}
}