rea*_*f02 144 uikit uiview ios
虽然大多数苹果文档编写得很好,但我认为" iOS事件处理指南 "是一个例外.我很难清楚地了解那里描述的内容.
该文件说,
在命中测试中,窗口调用
hitTest:withEvent:
视图层次结构的最顶层视图; 此方法通过递归调用pointInside:withEvent:
视图层次结构中返回YES的每个视图继续进行,继续向下移动层次结构,直到找到触摸发生在其边界内的子视图.该视图成为热门测试视图.
那么只有hitTest:withEvent:
系统调用最顶层的视图,调用pointInside:withEvent:
所有子视图,如果从特定子视图返回是YES,那么调用pointInside:withEvent:
该子视图的子类?
pgb*_*pgb 294
我认为你混淆了子视图与视图层次结构.该文件所说的内容如下.假设您有此视图层次结构.按层次结构,我不是在讨论类层次结构,而是在视图层次结构中查看,如下所示:
+----------------------------+
|A |
|+--------+ +------------+ |
||B | |C | |
|| | |+----------+| |
|+--------+ ||D || |
| |+----------+| |
| +------------+ |
+----------------------------+
Run Code Online (Sandbox Code Playgroud)
说你把手指放进去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
将是热门测试视图MHC*_*MHC 170
这似乎是一个非常基本的问题.但我同意你的意见,文件不像其他文件那么清楚,所以这是我的答案.
hitTest:withEvent:
在UIResponder中的实现执行以下操作:
pointInside:withEvent:
的self
hitTest:withEvent:
返回nil
.故事的结尾.hitTest:withEvent:
消息发送到其子视图.它从顶级子视图开始,并继续到其他视图,直到子视图返回非nil
对象,或者所有子视图都接收到该消息.nil
在第一次返回非对象,则第一次hitTest:withEvent:
返回该对象.故事的结尾.nil
对象,则第一个hitTest:withEvent:
返回self
此过程以递归方式重复,因此通常最终返回视图层次结构的叶视图.
但是,您可以覆盖hitTest:withEvent
以执行不同的操作.在许多情况下,覆盖pointInside:withEvent:
更简单,并且仍然提供了足够的选项来调整应用程序中的事件处理.
onm*_*133 45
我发现iOS中的Hit-Testing非常有用
- (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;
}
Run Code Online (Sandbox Code Playgroud)
编辑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
}
Run Code Online (Sandbox Code Playgroud)
Lio*_*ion 21
感谢您的回答,他们帮我解决了"叠加"视图的情况.
+----------------------------+
|A +--------+ |
| |B +------------------+ |
| | |C X | |
| | +------------------+ |
| | | |
| +--------+ |
| |
+----------------------------+
Run Code Online (Sandbox Code Playgroud)
假设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;
}
Run Code Online (Sandbox Code Playgroud)
userInteractionEnabled
设置为的视图发送触摸事件NO
;self
,self
将被视为潜在结果.请注意,[self.subviewsreverseObjectEnumerator]
需要遵循从最顶部到底部的视图层次结构.并检查clipsToBounds
以确保不测试蒙版子视图.
用法:
hitTest:withEvent:
为此- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self overlapHitTest:point withEvent:event];
}
Run Code Online (Sandbox Code Playgroud)
官方Apple的指南也提供了一些很好的插图.
希望这有助于某人.
iOS 触摸事件
1. user made a touch
2. system creates an event object with global coordinate of touch
3. hit testing by coordinate - find First Responder
4.1 send Touch Event to `UIGestureRecognizer`. After handling the touch can or can not(depends on setup) be forward to the First Responder
4.2 send Touch Event to the First Responder
4.2.1 handle Touch Event
Run Code Online (Sandbox Code Playgroud)
类图
3 命中测试
点击测试来查找第一响应者 - 检查 UIView 及其后继者的层次结构(例如 UIWindow)。从最大(后)UIView(UIWindow是起点/根点)到最小(前)UIView开始递归地找到前视图。因此,在这种情况下,返回的First Responder
是一个顶部(最小的)方法(内部使用) 。UIView
point()
hitTest()
point()
true
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
func point(inside point: CGPoint, with event: UIEvent?) -> Bool
Run Code Online (Sandbox Code Playgroud)
内部hitTest()
考虑
point() == true
point()
考虑超级视图内的矩形isUserInteractionEnabled == true
isHidden == false
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
}
Run Code Online (Sandbox Code Playgroud)
笔记:
view.isUserInteractionEnabled = false
此视图及其所有子视图时,将不会处理触摸事件point()
考虑位于超级视图中的矩形。这意味着如果触摸发生在从超级视图中绘制的视图部分上,则该视图及其所有子视图将不会处理触摸事件4 发送UI事件
UIKit
创建UIEvent
并发送UIApplication.shared.sendEvent()
至main event loop
[About]。UIEvent contains one or more
UITouch which contains -
UIView`,位置...
总会经历UIApplication.sendEvent() -> UIWindow.sendEvent() -> <First_Responder>
UIApplication.sendEvent() -> UIWindow.sendEvent() -> <First_Responder>.touchesBegan()
UIApplication.sendEvent() -> UIWindow.sendEvent() -> <First_Responder>.touchesEnded()
Run Code Online (Sandbox Code Playgroud)
//UIApplication.shared.sendEvent()
//UIApplication, UIWindow
func sendEvent(_ event: UIEvent)
Run Code Online (Sandbox Code Playgroud)
4.1 发送触摸事件到UIGestureRecognizer
这是一种简单方便的手势操作方式。有一些开箱即用的,UIGestureRecognizer
例如UITapGestureRecognizer
,UISwipeGestureRecognizer
...您可以创建自己的
let tap_v_0_2 = UITapGestureRecognizer(target: self, action: #selector(self.onView_0_2))
view_0_2.addGestureRecognizer(tap_v_0_2)
@objc func onView_0_2() {
print("---> onView V_0_2")
}
Run Code Online (Sandbox Code Playgroud)
UIGestureRecognizer
系统尝试在视图层次结构中查找。从第一响应者(使用UIView.superview
)开始,直到UIWindow
( 的子类UIView
)。这意味着如果您设置UIGestureRecognizer
了UIWindow
并且第一响应者UIView_0
没有UIGestureRecognizer
-UIWindow.UIGestureRecognizer
将被调用
有一些函数可以处理之间的UIGestureRecognizer
工作UIGestureRecognizerDelegate
以及一些处理转发事件到响应者链的函数,例如:cancelsTouchesInView
,,delaysTouchesBegan
delaysTouchesEnded
4.2 发送触摸事件到First Responder
这是更底层、更高级的方法,您可以在其中自定义逻辑。要使用它,您应该继承并UIView
重写一些方法,例如:
//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?)
Run Code Online (Sandbox Code Playgroud)
当 UIView_0_2 是第一响应者时:
4.2.1 处理触摸事件
当找到 FirstRessponder 时,就可以使用以下方法处理触摸事件了Responder Chain
响应者链
它是一种chain of responsibility
延伸的图案UIResponder
。每个UIResponder
都有next
指向链中下一个响应者的属性
默认情况下:
UIView.next
-> 超级视图?UIViewController
UIViewController.next
-> UINavigationController
??UIWindow
UIWindow.next
->UIApplication
摘要图:
从 UIView_0_2 开始打印响应者链:
UIView_0_2 -> UIView_0 -> ViewController -> UIDropShadowView -> UITransitionView -> CustomWindow -> UIWindowScene -> CustomApplication -> AppDelegate
Run Code Online (Sandbox Code Playgroud)
如果有触摸事件:
touchesBegan()
...)如果此方法未被覆盖 -UIResponder.next
用于查找下一个响应者并尝试在那里调用此方法super.touchesBegan()
在内部调用override func touchesBegan()
-UIResponder.next
用于查找下一个响应者并尝试在那里调用此方法例如,如果第一响应者是 UIView_0_2 但在 UIWindow 中重写了 TouchBegan()(不在 UIView_0_2 中)- UIWindow 将处理此触摸事件
UIWindow hitTest BEGIN
UIWindow point result:true
UIView_0 hitTest BEGIN
UIView_0 point result:true
UIView_0_1 hitTest BEGIN
UIView_0_1 point result:false
UIView_0_1 hitTest END result: nil
UIView_0_2 hitTest BEGIN
UIView_0_2 point result:true
UIView_0_2_1 hitTest BEGIN
UIView_0_2_1 hitTest END result: nil
UIView_0_2 hitTest END result: UIView_0_2
UIView_0 hitTest END result: UIView_0_2
UIWindow hitTest END result: UIView_0_2
Run Code Online (Sandbox Code Playgroud)
我们来看一个例子:
响应者链的附加用途
Responder chain
UIControl.addTarget()
也被事件总线等方法使用UIApplication.sendAction()
。
customButton.addTarget(nil, action: #selector(CustomApplication.onButton), for: .touchUpInside)
//target is nil. In other cases Responder Chain is not ussed
UIApplication.shared.sendAction(#selector(CustomApplication.foo), to: nil, from: self, for: nil)
Run Code Online (Sandbox Code Playgroud)
它在内部使用UIResponder.target()
和UIResponder.canPerformAction()
。当第一个 UIRessponder 启动流程时 -target()
被调用,如果super.target()
在内部调用override func target()
thencanPerformAction()
被调用,如果canPerformAction()
返回 false thennext
用于查找下一个 UIResponder 并递归地重复这些步骤,如果canPerformAction()
返回 true(默认情况下在该对象中找到选择器)然后该目标将递归返回并用于调用选择器
笔记:
canPerformAction()
返回 true 时,会抛出下一个错误:无法识别的选择器发送到实例
例如 - 您UIApplication.shared.sendAction(#selector(CustomApplication.foo), to: nil, from: self, for: nil)
从UIViewController
and 调用(如您所见)foo
选择器位于CustomApplication
( UIApplication
)
UIViewController target Begin
UIViewController canPerformAction result:false
UIViewController next is: UIWindow
UIWindow target Begin
UIWindow canPerformAction result:false
UIWindow next is UIApplication
UIApplication target begin
UIApplication canPerformAction result:true
UIApplication target result: UIApplication
UIWindow target result: UIApplication
UIViewController target result: UIApplication
//foo method body logic
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
76014 次 |
最近记录: |