学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

事件传递和响应机制

响应者对象(UIResponder)

继承了UIResponder的对象才能接受并处理事件,我们称之为“响应者对象”。以下都是继承UIResponder的,所以都能接收并处理事件。

1
2
3
UIApplication
UIViewController
UIView

为什么继承了UIResponder的类就能够接收并处理事件呢?
因为UIResponder中提供了以下4个对象方法来处理触摸事件

1
2
3
4
5
6
7
8
9
10
11
//UIResponder内部提供了以下方法来处理事件触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
//加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
//远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;

事件的产生和传递

事件的产生

  1. 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中
  2. UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序的主窗口(keywindow)。
  3. keywindow会在视图层次结构中找到一个最合适的视图来处理触摸事件。

寻找处理事件最合适的view

  1. 首先判断主窗口(keyWindow)自己是否能接受触摸事件
  2. 判断触摸点是否在自己身上
  3. 子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
  4. view,比如叫做hitView,那么会把这个事件交给这个hitView,再遍历这个hitView的子控件,直至没有更合适的view为止。
  5. 如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。

UIView不能接收触摸事件的三种情况:

  1. 不允许交互:userInteractionEnabled = NO
  2. 隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件
  3. 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判断自己能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;

// 2.判断点在不在当前控件上面
if (![self pointInside:point withEvent:event]) return nil;

// 3.去找有没有比自己更合适的view
// 从后往前遍历自己的子控件
int count = self.subviews.count;

for (int i = count - 1; i >= 0; i--) {
// 获取子控件
UIView *childView = self.subviews[i];

// 转换坐标系
// 把自己坐标系上的点转换成子控件做坐标系上的点
CGPoint childPoint = [self convertPoint:point toView:childView];

UIView *fitView = [childView hitTest:childPoint withEvent:event];
// 找到最合适的view
if (fitView) {
return fitView;
}

}

// 没有找到比自己更合适的view
return self;
}

拦截事件的处理

通过重写hitTest:withEvent:就可以拦截事件的传递过程,想让谁处理事件谁就处理事件。

注意

如果hitTest:withEvent:方法中返回nil,那么调用该方法的控件本身和其子控件都不是最合适的view,也就是在自己身上没有找到更合适的view。那么最合适的view就是该控件的父控件。

技巧

想让谁成为最合适的view就重写谁自己的父控件的hitTest:withEvent:方法返回指定的子控件,或者重写自己的hitTest:withEvent:方法 return self。但是,建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view!

原因是子控件在遍历时会根据addsuview的逆序遍历。

文章参考

  1. iOS之事件的传递和响应机制