1.响应者对象
- 在iOS中不是所有对象都可以响应事件的,只有继承了UIResponder的对象才能响应事件。称为响应者对象。
- UIApplication、UIViewController、UIView都继承自UIResponder,所以都是响应者对象,都可以响应事件。
2.UIResponder
UIResponder内部的方法
- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet *)touches API_AVAILABLE(ios(9.1));
- (void)pressesBegan:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));
- (void)pressesChanged:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));
- (void)pressesEnded:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));
- (void)pressesCancelled:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event API_AVAILABLE(ios(3.0));
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event API_AVAILABLE(ios(3.0));
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event API_AVAILABLE(ios(3.0));
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event API_AVAILABLE(ios(4.0));
3.事件传递链
当发生事件时,系统将事件以UIEvent的方式加入到UIApplication事件队列中,UIApplication从事件队列中取出最新的事件分发传递到window中,window会通过hitTest: withEvent: 方法找出当前点所在的视图,然后把事件传递给该视图,若该视图下面仍然有子控件,则依次把事件传递给他的可以响应事件的子控件(子控件的传递顺序为最后添加的最先传递到)
UIApplication -> UIWindow -> 事件所在点的最顶级的View -> subView ->···
4.事件响应链
当事件按事件传递链的原则传递下去的过程中,若在最后一个子控件得到了响应,则直接响应,若没有响应,则按照事件传递链相反的顺序传递,若UIApplication也没有响应事件,则将事件丢弃。
subView->supView->···->事件所在点的最顶级的view->UIWindow->UIApplocation
5.两个核心方法
返回一个View,返回的View响应当前的事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判断点击的点是否在视图返回内,返回Yes会继续往他的子视图传递事件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
6.用案例探究这两个方法的调用
创建了三个继承自UIButton的类分别为WYYButton1,WYYButton2,WYYButton3
WYYButton1.m中的代码
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"btn1的hit方法被调用");
return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"btn1的pointInside方法被调用");
return YES;
}
WYYButton2.m中的代码
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"btn2的hit方法被调用");
return self;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"btn2的pointInside方法被调用");
return YES;
}
WYYButton3.m中的代码
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"btn3的hit方法被调用");
return nil;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"btn3的pointInside方法被调用");
return YES;
}
在控制器中分别用WYYButton1, WYYButton2,WYYButton3创建三个按钮btn1,btn2,btn3。其中btn2和btn3为btn1的子控件
WYYButton1 *btn1 = [[WYYButton1 alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
btn1.backgroundColor = [UIColor orangeColor];
[self.view addSubview:btn1];
[btn1 addTarget:self action:@selector(btn1Click) forControlEvents:UIControlEventTouchUpInside];
WYYButton2 *btn2 = [[WYYButton2 alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
btn2.backgroundColor = [UIColor redColor];
[btn1 addSubview:btn2];
[btn2 addTarget:self action:@selector(btn2Click) forControlEvents:UIControlEventTouchUpInside];
WYYButton3 *btn3 = [[WYYButton3 alloc] initWithFrame:CGRectMake(0, 100, 200, 100)];
btn3.backgroundColor = [UIColor blueColor];
[btn1 addSubview:btn3];
[btn3 addTarget:self action:@selector(btn3Click) forControlEvents:UIControlEventTouchUpInside];
此时点击btn3按钮,控制台的打印结果为
btn1的hit方法被调用
btn1的pointInside方法被调用
btn3的hit方法被调用
btn2的hit方法被调用
btn1的hit方法被调用
btn1的pointInside方法被调用
btn3的hit方法被调用
btn2的hit方法被调用
btn2点击
此时把btn1中的**- (UIView )hitTest:(CGPoint)point withEvent:(UIEvent )event的返回值改为nil,点击btn3,打印结果为
btn1的hit方法被调用
btn1的hit方法被调用
还原上面的修改,把btn1的*- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent )event的返回值改为NO,点击btn3,打印结果为
btn1的hit方法被调用
btn1的pointInside方法被调用
btn1的hit方法被调用
btn1的pointInside方法被调用
由以上结果可以知道,当window把事件传递给View时,会先调用hitTest方法,在hitTest方法中会调用pointInside方法,若pointInside方法返回NO,则表示触摸点不在当前View中,则事件不再向子控件传递,若返回YES,则继续继续向下传递
通过修改btn1的userInteractionEnabled,alpha,hidden可以得出结论
当
btn1.userInteractionEnabled = NO;
btn1.alpha <= 0.01;
btn1.hidden = YES;
中任意一个或多个条件满足时,事件也不会继续传递
对于上面两次调用方法的原因暂时不清楚,搜了一下,看到一个结果是说问苹果那边说是正常的
7.模拟系统实现
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//不允许用户交互则返回nil
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}
//触摸点不在当前View的位置中则返回nil
if (![self pointInside:point withEvent:event]) {
return nil;
}
UIView *hitView = self;
for (UIView *subView in self.subviews) {
//坐标转换,把当前坐标系上的点转换成子控件坐标系上的点
CGPoint convertPoint = [self convertPoint:point toView:subView];
hitView = [subView hitTest:convertPoint withEvent:event];
if (hitView) {
break;
}
}
return hitView;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
if (point.x < self.frame.size.width && point.y < self.frame.size.height && point.x >= 0 && point.y >= 0) {
return YES;
}
return NO;
}