事件传递机制
- 当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
- UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
- UIWindow将事件向下分发,即UIView。
- UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
- 遍历子控件,重复3,4两步。
- 如果没有找到,那么自己就是事件处理者。如果自己不能处理,那么不做任何处理。
- 注意:其中 UIView不接受事件处理的情况主要有以下三种
- alpha <0.01
- userInteractionEnabled = NO
- hidden = YES
此外在遍历子控件的时候是倒序遍历,此目的是为了让最上层添加的view最先响应事件
整理整个流程如下:
UIApplication -> UIWindow -> 倒序遍历子控件 ->找到能响应的view -> 倒序遍历子控件 ->....->找到最终响应的view
事件传递主要依赖的是以下两个函数:
hitTest:(CGPoint)point withEvent:(UIEvent *)event
pointInSide:(CGPoint)point withEvent:(UIEvent *)event
如果想要自行更改响应视图,可以重写hitTest:withEvent
方法,指定要响应事件的view即可
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"-----%@",self.nextResponder.class);
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) return nil;
//判断点在不在这个视图里
if ([self pointInside:point withEvent:event]) {
//在这个视图 遍历该视图的子视图
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
//转换坐标到子视图
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
//递归调用hitTest:withEvent继续判断
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
//在这里打印self.class可以看到递归返回的顺序。
return hitTestView;
}
}
//这里就是该视图没有子视图了 点在该视图中,所以直接返回本身,上面的hitTestView就是这个。
NSLog(@"命中的view:%@",self.class);
return self;
}
//不在这个视图直接返回nil
return nil;
}
下面示例是定义一个button的可点击区域
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"%@ -- pointInside",self.class);
CGRect bounds = self.bounds;
//若原热区小于200x200,则放大热区,否则保持原大小不变
//一般热区范围为40x40 ,此处200是为了便于检测
CGFloat widthDelta = MAX(200 - bounds.size.width, 0);
CGFloat heightDelta = MAX(200 - bounds.size.height, 0);
bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
return CGRectContainsPoint(bounds, point);
}
响应机制
响应机制的顺序和事件传递是反着的,事件传递是从上层往下层传递,像剥洋葱一样,找到最终响应事件的view,响应机制则是从下层向上层,响应机制主要依赖的方法是:
touchesBegin , touchesMoved, touchesEnded
等方法

响应链是通过nextResponder属性组成的一个链表。
点击的view有 superView,nextResponder就是superView;
view.nextResponder.nextResponder是viewController 或者是 view.superView. view
view.nextResponder.nextResponder.nextResponder是 UIWindow (非严谨,便于理解)
view.nextResponder.nextResponder.nextResponder. nextResponder是UIApplication、UIAppdelate、直到nil (非严谨,便于理解)
touch事件就是根据响应链的关系来层层调用(我们重写touch 要记得 super 调用,不然响应链会中断)。
比如我们监听self.view的touch事件,也是因为subviews的touch都在同一个响应链里。
要注意的是,UIControl, UIScrollView和手势都可以阻断响应链,如果不想被阻断,则可以重写hitTest:withEvent
方法
网友评论