事件传递响应链
UIResponder
在iOS中,能够响应事件的对象都是UIResponder的子类对象。UIResponder提供了四个点击的回调方法,分别对应用户点击开始、移动、结束和取消,其中只有在程序强制退出或者来电时,取消点击事件才会调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
|
##1. 响应链的传递
iOS应用程序加载时会先执行main函数
1 2 3 4 5 6 7 8 9 10
| int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
|
依次加载:UIApplication->AppDelegate->UIWindow->UIViewController->superView->subviews
关系为:[UIApplication sharedApplication].keyWindow.rootViewController.view.subviews
事件的产生和传递
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序主窗口(keyWindow)。
主窗口会在视图层次结构中找到一个最适合的视图来处理当前触摸事件,这也是整个事件处理过程的第一步。
找到合适的视图控件后,就会调用视图控件的touches方法来做具体事件的处理。
2. Hit-Testing机制
iOS使用Hit-Testing寻找触摸的view。 Hit-Testing通过检查触摸点是否在关联的view边界内,如果在,则递归地(recursively)检查该view的所有子view。在层级上处于最近且边界范围包含触摸点的view成为hit-test view。确定hit-test view后,它传递触摸事件给该view。
重写hitTest
方法
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| - (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.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger 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;
}
}
// 循环结束,表示没有比自己更合适的view
return self;
}
|
响应链的事件传递过程
如果当前view的控制器存在,就传递给控制器;如果控制器不存在,则将其传给它的父视图。
在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,此时会将事件或消息传递给window对象进行处理。
如果window对象也不处理,则将事件或消息传递给UIApplication对象。
如果UIApplication也不能处理该事件或消息,则将其丢弃。