1、UITouch
一个手指一次触摸屏幕,就对应生成一个 UITouch 对象,每个 UITouch 对象记录了一些触摸信息(触摸事件、位置、阶段状态、所处的视图、窗口等信息),一个触摸事件可能有多个手指触摸产生的。
2、UIEvent
UIEvent 分为不同的事件类型,一个触摸事件会对应一个 UIEvent 对象,UIEvent 对象中包含了触发该对象的触摸对象集合。
3、UIResponder
用于处理事件的 API,可以处理触摸、按压 远程控制、硬件运动事件等。不止是用来接收事件,同时也可以处理和传递对应的事件,如果当前响应着不能处理,则转发给其他合适的响应着处理。
4、事件的传递,寻找事件的第一响应着
应用程序接收到触摸事件以后,将事件加入 UIApplication 事件队列,等到处理该事件时,将该队列出队列,UIApplication 将事件传递给窗口对象 UIWindow,如果能响应时间,则从后向前遍历子视图,直到找到第一响应着
同级的视图,会先遍历后加入的子视图
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
当视图 不允许交互、隐藏、透明度<0.01 则是无法响应事件的,
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//3种状态无法响应事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
//触摸点若不在当前视图上则无法响应事件
if ([self pointInside:point withEvent:event] == NO) return nil;
//从后往前遍历子视图数组
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)
{
//如果子视图中有更合适的就返回
return fitView;
}
}
//没有在子视图中找到更合适的响应视图,那么自身就是最合适的
return self;
}
5、事件拦截
如果子视图点击范围超出父视图,为使得点击子视图超出部分,可以响应,则需要 再父视图上进行拦截,执行下面的代码:
#import "FJFTabbar.h"
@implementation FJFTabbar
//TabBar
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//将触摸点坐标转换到在CircleButton上的坐标
CGPoint pointTemp = [self convertPoint:point toView:self.indicateView];
//若触摸点在CricleButton上则返回YES
if ([self.indicateView pointInside:pointTemp withEvent:event]) {
return YES;
}
//否则返回默认的操作
return [super pointInside:point withEvent:event];
}
@end
6、事件的响应及传递
自上而下,沿着响应者链进行传递,如果拦截不向下分发事件,则需要重写:touchesBegan:withEvent: 且不调用父类的 touchesBegan:withEvent:方法,如果拦截但是继续往下分发事件 则调用父类的 touchesBegan:withEvent:方法
UIApplication 的 nextResponder 是 app delegate
7、UIGestureRecognizer、UIControl
UIGestureRecognizer、UIControl 也能够响应触摸事件,但是和 UITouch 事件又有区别
UIWindow 将事件传递给第一响应者之前,会先将事件传递给相关的手势识别器,如果手势识别器识别成功,则会取消 第一响应者对事件的响应,如果没有识别成功,则第一响应者会接收事件的处理,即 UIGestureRecognizer 比 UIResponder 具有更高的优先级
8、cancelsTouchesInView
默认为 yes,表示手势识别器成功识别了手势之后,会通知 Application 取消响应链对事件的响应,并不再传递事件给第一响应者。
9、delaysTouchesBegan
默认为no,默认情况下,手势识别器在识别手势期间,当触摸状态发生改变时,Application 会将事件传递给手势识别器和第一响应者,如果设置为yes,则不会发送给第一响应者
10、delaysTouchesEnded
默认为yes,当手势识别失败时,若此时触摸已经结束,会延迟一段时间(0.15s)再调用响应者的 touchesEnded:,若设置成 no,则会在手势识别失败时,立即通知 Application 发送状态为 end 的 touch 事件给第一响应者 以调用 touchesEnded: 结束事件响应。
11、UIControl
是 target-action 模式处理触摸事件的控件 beginTrackingWithTouch: 是在 touchesBegan: 的内部调用
系统提供有默认 action 操作的 UIControl 优先级最高,其次是 手势识别器,然后是 自定义的 UIControl,然后是 UITouch
12、 事件完整响应链
- 用户触摸屏幕,IOKit收到屏幕操作,会将这册操作封装成IOHIDEvent对象,通过 mach port 将事件转发给 Springboard 来处理。
- Springboard 是iOS 系统的桌面程序,Springboard收到 mach port 发过来的事件 唤醒main runloop 来处理
- Springboard的 main runloop 将事件交给当前应用程序的 source1处理,这时会唤醒当前应用程序的 runloop,当前应用程序的 source1 会调用__IOHIDEventSystemClientQueueCallback() 函数,该函数内部会判断是否有程序在前台显示,如果有则通过 mach port 将IOHIDEvent事件转发给这个程序。如果没有,则表明 Springboard 的桌面程序没有在前台显示,函数会将事件交给 source0 处理,source0会调用该函数,函数内部做具体的处理操作
- UIApplication 管理事件队列
- 通过 hitTest: pointInSide 操作,去寻找第一响应者
- 如果第一响应者 非 UIControl 子类 ,也没有手势识别器,则第一响应者具有最高优先级,因此 UIApplication 会先将事件传递给它进行处理,首先 UIApplication 通过 sendEvent: 传递给 window,window再通过 sendEvent: 传递给 第一响应者,第一响应者进行事件处理,如果不处理则传递给下一响应者,最后传递给 UIApplication,如果 UIApplication 实现了代理 ,会交给UIApplicationDelegate,如果 UIApplicationDelegate没有处理则事件会被丢弃。
- 如果第一响应者 非 UIControl 子类 ,但是绑定了手势识别,window 会先将事件发送给手势识别器,再发送给第一响应者
- 如果第一响应者 是自定义 UIControl 子类 ,但是绑定了手势识别,首先响应手势识别器
- 如果第一响应者 是 UIControl 系统子类 ,但是绑定了手势识别 则先响应 target-action