iOS的事件传递和响应

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;
}

你可能感兴趣的:(iOS的事件传递和响应)