虽然大多数苹果文档都写得很好,但我认为《iOS事件处理指南》是个例外。我很难清楚地理解那里所描述的内容。
文件说,
在命中测试中,窗口在视图层次结构的最顶层视图上调用hitTest:withEvent:
;这个方法通过在返回YES的视图层次结构中的每个视图上递归地调用pointInside:withEvent:
来继续,沿着层次结构继续,直到它找到在其边界内发生触摸的子视图。该视图成为命中测试视图。
那么,是不是只有最顶层视图的hitTest:withEvent:
被系统调用,它调用所有子视图的pointInside:withEvent:
,如果从特定子视图返回的是YES,那么调用该子视图的子类的pointInside:withEvent:
?
7条答案
按热度按时间uqdfh47h1#
我认为你混淆了子类和视图层次结构。医生说的话如下。假设你有这个视图层次结构。通过层次结构,我不是在谈论类层次结构,而是在视图层次结构中的视图,如下所示:
假设你把手指放在
D
里面。接下来会发生什么hitTest:withEvent:
是在A
上调用的,A
是视图层次结构中最顶层的视图。pointInside:withEvent:
在每个视图上被递归调用。1.在
A
上调用pointInside:withEvent:
,并返回YES
1.在
B
上调用pointInside:withEvent:
,并返回NO
1.在
C
上调用pointInside:withEvent:
,并返回YES
1.在
D
上调用pointInside:withEvent:
,并返回YES
1.在返回
YES
的视图上,它将向下查看层次结构,以查看发生触摸的子视图。在这种情况下,从A
,C
和D
,它将是D
。D
将是命中测试视图kiz8lqtg2#
这似乎是一个很基本的问题。但我同意你的观点,这份文件不像其他文件那么清楚,所以我的答案是这样的。
hitTest:withEvent:
在UIResponder中的实现如下:self
的pointInside:withEvent:
hitTest:withEvent:
返回nil
。故事的结尾。hitTest:withEvent:
消息发送到其子视图。它从顶层子视图开始,并继续到其他视图,直到子视图返回非nil
对象,或者所有子视图都接收到该消息。nil
对象,则第一个hitTest:withEvent:
返回该对象。故事的结尾。nil
对象,则第一个hitTest:withEvent:
返回self
这个过程递归地重复,所以通常最终返回视图层次结构的叶视图。
但是,您可以重写
hitTest:withEvent
以执行不同的操作。在许多情况下,重写pointInside:withEvent:
更简单,并且仍然提供足够的选项来调整应用程序中的事件处理。rseugnpd3#
我觉得这个Hit-Testing in iOS很有帮助
编辑Swift 4:
iszxjhcz4#
感谢您的回答,他们帮助我解决了“叠加”视图的情况。
假设
X
-用户的触摸。B
上的pointInside:withEvent:
返回NO
,因此hitTest:withEvent:
返回A
。我在UIView
上写了一个类别来处理当你需要在顶部最可见的视图上接收触摸时的问题。1.我们不应该发送隐藏或透明视图的触摸事件,或者将
userInteractionEnabled
设置为NO
的视图;1.如果触摸在
self
内,则self
将被视为潜在结果。1.递归检查所有子视图是否命中。如果有的话,把它还回去。
1.否则返回self或nil,取决于步骤2的结果。
注意,
[self.subviewsreverseObjectEnumerator]
需要遵循从顶部到底部的视图层次结构。并检查clipsToBounds
以确保不测试掩码子视图。使用方法:
1.在子类视图中导入类别。
1.将
hitTest:withEvent:
替换为苹果官方指南也提供了一些很好的插图。
希望这对某人有帮助。
flseospp5#
就像这个片段!
3mpgtkmj6#
iOS触控事件
类图
3命中测试
点击测试以找到第一响应者-检查UIView及其后继者的层次结构(例如,从最大的(后)UIView(UIWindow是起始点/根点)到最小的(前)UIView递归地找到前视图。因此,在这种情况下,
First Responder
是顶部(最小的)UIView
,point()
方法(hitTest()
在内部使用point()
),其中返回true
。内部
hitTest()
考虑到point() == true
point()
考虑了在超级视图内的矩形isUserInteractionEnabled == true
isHidden == false
备注:
view.isUserInteractionEnabled = false
这个视图和它的所有子视图将不处理触摸事件point()
会考虑放置在超级视图中的矩形。这意味着如果一个触摸发生在一个视图部分,这是绘制出的superview,这个视图和它的所有子视图将不会处理触摸事件UIView.clipsToBounds(https://stackoverflow.com/a/73513083/4770877)
4发送UI事件
UIKit
创建UIEvent
,由UIApplication.shared.sendEvent()
发送到main event loop
About(https://stackoverflow.com/a/75204210/4770877)。UIEvent contains one or more
触摸which contains -
UIView,位置. 它总是通过
UIApplication.sendEvent() -> UIWindow.sendEvent() -> <First_Responder>`4.1将Touch Event发送到
UIGestureRecognizer
这是一种简单而方便的手势操作方法。有一些开箱即用的
UIGestureRecognizer
像UITapGestureRecognizer
,UISwipeGestureRecognizer
.你就能创造出你自己的系统尝试在视图层次结构中查找
UIGestureRecognizer
。从第一个响应者(使用UIView.superview
)开始,直到UIWindow
(UIView
的子类)。这意味着如果您在UIWindow
上设置UIGestureRecognizer
,并且第一响应者是UIView_0
而没有UIGestureRecognizer
-UIWindow.UIGestureRecognizer
将被调用在
UIGestureRecognizerDelegate
中有一些函数可以处理UIGestureRecognizer
之间的工作还有一些函数来处理转发事件到响应器链,比如:
cancelsTouchesInView
、delaysTouchesBegan
、delaysTouchesEnded
4.2向
First Responder
发送触摸事件这是一种更低级、更高级的方法,您可以在其中自定义逻辑。要使用它,你应该从
UIView
继承并覆盖一些方法,比如:当UIView_0_2是第一响应者时:
4.2.1处理触摸事件
找到FirstResponder时,是时候使用
Responder Chain
处理触摸事件了响应链
它是一种对
UIResponder
进行扩展的chain of responsibility
模式。每个UIResponder
都有next
属性,指向链中的下一个响应者默认情况下:
UIView.next
-> superview??UIViewController
UIViewController.next
->UINavigationController
??UIWindow
UIWindow.next
->UIApplication
摘要图:
从UIView_0_2开始打印响应器链:
如果发生触摸事件:
touchesBegan()
...)如果此方法未被覆盖-UIResponder.next
用于查找下一个响应者并尝试在那里调用此方法override func touchesBegan()
内部调用super.touchesBegan()
-UIResponder.next
用于查找下一个响应者并尝试在那里调用此方法例如,如果第一个响应者是UIView_0_2,但在UIWindow中覆盖了touchesBegan()(而不是在UIView_0_2中)- UIWindow将处理此触摸事件
让我们来看看一个例子:
Responder Chain的其他用法
Responder chain
也用于UIControl.addTarget()
或UIApplication.sendAction()
方法,如事件总线。内部使用
UIResponder.target()
和UIResponder.canPerformAction()
。当第一个UIResponder开始一个流时-target()
被调用,如果override func target()
内部调用super.target()
,则canPerformAction()
被调用,如果canPerformAction()
返回false,则next
用于查找下一个UIResponder并递归地重复这些步骤,如果canPerformAction()
返回true(默认情况下,选择器在此对象中找到),则此目标将递归返回,并用于调用选择器备注:
canPerformAction()
返回true时,会抛出下一个错误:发送到示例的无法识别的选择器
例如,您从
UIViewController
调用UIApplication.shared.sendAction(#selector(CustomApplication.foo), to: nil, from: self, for: nil)
,并且(如您所见)foo
选择器位于CustomApplication
(UIApplication
)中Android onTouch(https://stackoverflow.com/a/57222691/4770877)
qqrboqgw7#
@lion的片段就像一种魅力。我把它移植到Swift 2.1,并把它作为UIView的扩展。我把它贴在这里以防有人需要。
要使用它,只需在uiview中覆盖 hitTest:point:withEvent,如下所示: