事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的
view
、寻找最合适的
view
的底层实现、拦截事件的处理)
->
找到最合适的
view
后事件的处理(
touches
方法的重写,也就是事件的响应)
iOS
中的事件可以分为
3
大类型:
1. 触摸事件
2. 加速计事件
1. 触摸事件
2. 加速计事件
3.
远程控制事件
每当我们点击了一下
iOS
设备的屏幕,
UIKit
就会生成一个事件对象
UIEvent
,然后会把这个
Event
分发给当前
active
的
app(
官方原文说:
Then it places the event object
in
the active app’s event queue.)
告知当前活动的 app 有事件之后, UIApplication 单例就会从事件队列中去取最新的事件,然后分发给能够处理该事件的对象。 UIApplication 获取到 Event 之后, Application 就纠结于到底要把这个事件传递给谁,这时候就要依靠 HitTest 来决定了。
告知当前活动的 app 有事件之后, UIApplication 单例就会从事件队列中去取最新的事件,然后分发给能够处理该事件的对象。 UIApplication 获取到 Event 之后, Application 就纠结于到底要把这个事件传递给谁,这时候就要依靠 HitTest 来决定了。
iOS
中,
hit-Testing
的作用就是找出这个触摸点下面的
View
是什么,
HitTest
会检测这个点击的点是不是发生在这个
View
上,如果是的话,就会去遍历这个
View
的
subviews
,直到找到最小的能够处理事件的
view
,如果整了一圈没找到能够处理的
view
,则返回自身。来一个简单的图说明一下。
假设我们现在点击到了图中的 E , hit-testing 将进行如下步骤的检测 ( 不包含重写 hit-test 并且返回非默认 View 的情况 )
1 、触摸点在 ViewA 内,所以检查 ViewA 的 Subview B 、 C
2 、触摸点不在 ViewB 内,触摸点在 ViewC 内部,所以检查 ViewC 的 Subview D 、 E
3 、触摸点不在 ViewD 内,触摸点发生在 ViewE 内部,并且 ViewE 没有 subview ,所以 ViewE 属于 ViewA 中包含这个点的最小单位,所以 ViewE 变成了该次触摸事件的 hit-TestView
PS.
1 、默认的 hit-testing 顺序是按照 UIView 中 Subviews 的逆顺序
2 、如果 View 的同级别 Subview 中有重叠的部分,则优先检查顶部的 Subview ,如果顶部的 Subview 返回 nil , 再检查底部的 Subview
3 、 Hit-Test 也是比较聪明的,检测过程中有这么一点,就是说如果点击没有发生在某 View 中,那么该事件就不可能发生在 View 的 Subview 中,所以检测过程中发现该事件不在 ViewB 内,也直接就不会检测在不在 ViewF 内。也就是说,如果你的 Subview 设置了 clipsToBounds= NO , 实际显示区域可能超出了 superView 的 frame ,你点击超出的部分,是不会处理你的事件的,就是这么任性!
Hit-Test 的检查机制如上所示,当确定了 Hit-TestView 时,如果当前的 application 没有忽略触摸事件 (UIApplication:isIgnoringInteractionEvents), 则 application 就会去分发事件 (sendEvent:->keywindow:sendEvent:)
UIView 中提供两个方法用来确定 hit-testing View, 如下所示
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
当一个
View
收到
hitTest
消息时,会调用自己的
pointInside:withEvent:
方法
,
如果
pointInside
返回
YES
,则表明触摸事件发生在我自己内部,则会遍历自己的所有
Subview
去寻找最小单位
(
没有任何子
view)
的
UIView
,如果当前
View.userInteractionEnabled =
NO
,enabled=
NO
(UIControl),
或者
alpha<=
0.01
, hidden
等情况的时候,
hitTest
就不会调用自己的
pointInside
了,直接返回
nil
,然后系统就回去遍历兄弟节点。简而言之,可以写成这样
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden)
{
return nil;
}
BOOL inside = [self pointInside:point withEvent:event];
UIView *hitView = nil;
if (inside) {
NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
for (UIView *subview in enumerator) {
hitView = [subview hitTest:point withEvent:event];
if (hitView) {
break;
}
}
if (!hitView)
{
hitView = self;
}
return hitView;
} else {
return nil;
}
}