iOS中事件传递和事件响应
1.响应者对象
先介绍一下响应者对象,在iOS中并不是所有的对象都能够处理事件,只有继承UIResponder的对象才能够接收和处理事件,我们称为响应者对象。 UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都能接收并处理事件。
2.iOS中的事件
iOS中的事件可以分为三大类型:
触摸事件 、传感器事件 、远程控制事件
本文主要讨论触摸事件
3.事件传递和事件响应的区别
首先我们先来了解一下事件传递和事件响应的区别,以便更好的理解后面的内容 。(千万不要混淆这两者的概念)
事件的传递是从上到下的顺序 (UIApplication-->UIWindow-->递归找到最合适处理的View),即从父控件到子控件的寻找过程
事件响应是从下到上的顺序 (实现View的touchesBegan方法,如果该View不能处理事件,则向上传递给它的父控件),从子控件传递给父控件
4.iOS中事件的产生和传递
-
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,而不是放入栈中保存,因为队列的特点是FIFO(先进先出),先产生的事件先处理才符合常理
-
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序的主窗口(keyWindow)
-
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
事件传递最重要的就是找到合适的View来接收事件, 应用是如何找到最合适的View来接收处理事件的呢?
1.首先判断主窗口(keyWindow)自己是否能接受触摸事件
2.判断触摸点是否在自己身上
3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
4.view,比如叫做fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的子控件,直至没有更合适的view为止。
5.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。
有两个重要的方法 帮助应用寻找到最合适的View
hitTest:withEvent:方法 (自定义View 实现该方法)
- (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] == NO) return nil;
// 3.从后往前遍历子控件数组
int count = (int)self.subviews.count;
for (int i = count - 1; i >= 0; i--) {
// 获取子控件
UIView *childView = self.subviews[i];
// 坐标系的转换,把窗口上的点转换为子控件上的点
// 把自己控件上的点转换成子控件上的点
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) {
// 如果能找到最合适的view
return fitView;
}
}
// 4.没有找到更合适的view,也就是没有比自己更合适的view
return self;
}
pointInside:withEvent:方法
//判断当前点是否在View上
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//判断点在不在view区域
if (CGRectContainsPoint(self.btn.bounds, point)) {
return YES;
}
return NO;
}
事件的响应
找到合适的View之后就会调用该View的touches方法进行响应处理具体的事件,找不到最合适的View,就不会调用touches方法进行处理
touchesBegan方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
一般默认做法是控件将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理 (即调用super的touches方法)。
那么如何判断响应者的上一个响应者是谁呢?有以下两个规则
- 判断当前是否是控制器的View,如果是控制器的View,那么上一个响应者就是控制器
- 如果不是控制器的View,那么上一个响应者就是其父控件
如果控制器也不响应touches方法,就把事件交给UIWindow,如果UIWindow也不响应,交把事件给UIApplication,如果都不响应事件那么事件就会抛弃作废。
最后总结来说一次完整的触摸事件的传递响应过程为:
UIApplication-->UIWindow-->递归找到最合适处理的控件-->控件调用touches方法-->判断是否实现touches方法-->没有实现默认会将事件传递给上一个响应者-->找到上一个响应者-->找不到方法作废
注:以下三种情况不能接收触摸事件
- 不允许交互: 即空间的userInteractionEnabled = NO;
- 隐藏:隐藏的控件不能接收触摸事件
- 透明度:如果一个控件的透明度设置 <0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明