美文网首页iOS底层技术
IOS事件的传递与响应研究

IOS事件的传递与响应研究

作者: wg刚 | 来源:发表于2018-07-30 17:20 被阅读0次

响应者对象:能处理事件的对象,也就是继承自UIResponder的对象。
响应者链是由多个响应者对象连接起来的链条。
只有继承UIResponder的的类,才能处理事件,UIApplication、UIView、UIViewController都是继承自UIResponder类,可以响应和处理事件。CALayer不是UIResponder的子类,无法处理事件。
用队列管理事件,而不用栈:队列先进先出,能保证先产生的事件先处理。栈先进后出。
先看一个例子来引出相关概念:
如图:控制器的view上有一个子视图AView,在AView上有一个子视图BView

image.png

我们通过点击事件来打印出最合适的view:

#import "AView.h"

@implementation AView

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self) {
        NSLog(@"AView上最合适view是%@",@"AView");
        return self;
    }else if(view == self.subviews[0]){
        NSLog(@"AView上最合适view是%@",@"BView");
        return view;
    }else{
        NSLog(@"在AView上没有最合适的view");
        return nil;
    }
}
@end
==================================================
#import "BView.h"

@implementation BView

@end

==================================================

#import "ViewController.h"
#import "AView.h"
#import "BView.h"

@interface ViewController ()

@property (nonatomic, strong) AView *aView;
@property (nonatomic, strong) BView *bView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"ViewController";
    
    [self.view addSubview:self.aView];
    [_aView addSubview:self.bView];
}

-(AView *)aView{
    if (!_aView) {
        _aView = [[AView alloc] init];
        _aView.backgroundColor = [UIColor yellowColor];
        _aView.frame = CGRectMake(20, 100, 200, 200);
    }
    return _aView;
}

-(BView *)bView{
    if (!_bView) {
        _bView = [[BView alloc] init];
        _bView.backgroundColor = [UIColor greenColor];
        _bView.frame = CGRectMake(0, 0, 100, 100);
    }
    return _bView;
}
@end

执行结果:

333.gif

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法可以找到最合适的view成为事件的响应者。

但是他是怎么找到的呢?
我们队上面代码进行改写:

###//在AView.m文件中:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES) {
        NSLog(@"在AView上self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES导致它和其子视图没有最合适的view");
        return nil;
    }
   // 判断点在不在当前控件
    if (![self pointInside:point withEvent:event]) {
        NSLog(@"触摸事件不在AView上");
        return nil;
    }
    NSInteger count = self.subviews.count;
   //从外向里遍历自己的子控件
    for (NSInteger i = count-1; i>=0; i--) {
        UIView *subView = self.subviews[i];
        CGPoint subPoint = [self convertPoint:point toView:subView];
        UIView *suitView = [subView hitTest:subPoint withEvent:event];
        if (suitView) {
            NSLog(@"AView上最合适view是%@",@"BView");
            return suitView;
        }
    }
    NSLog(@"AView上最合适view是%@",@"AView");
    return self;
}

###//在BView.m文件中:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view) {
        if (view == self) {
            NSLog(@"BView上最合适view是%@",@"BView");
            return self;
        }else if(view == self.subviews[0]){
            NSLog(@"BView上最合适view是%@",@"CView");
            return view;
        }else{
            NSLog(@"BView上没有最合适的view");
            return nil;
        }
    }else{
        NSLog(@"触摸事件BView上");
        return nil;
    }
}

分别点击BView、AView和视图控制器对应的view,打印结果分别如下:
BView:

image.png

AView:

image.png

view:

image.png

我如果把AView的self.userInteractionEnabled设为NO

点击三个view的运行结果一样:

image.png

分析:

点击BView
1、事件从上面一步一步传到AView,执行hitTest:withEvent:发现AView符合要求(什么要求在下面总结),然后再开始遍历其子视图,看看是否有更合适的。
2、BView是AView的唯一子视图,执行BView的hitTest:withEvent:发现符合条件
3、而且BView上没有子视图,所以其成为最合适view,返回。

点击AView
1、事件从上面一步一步传到AView,执行hitTest:withEvent:发现AView符合要求(什么要求在下面总结),然后再开始遍历其子视图,看看是否有更合适的。
2、BView是AView的唯一子视图,执行BView的hitTest:withEvent:发现不符合条件(触摸点不在BView上)。所以AView就是最合适的view, 返回。

点击控制器对应的view
1、首先执行控制器view对应的hitTest:withEvent:,发现符合条件,
2、view上有子视图AView、BView在AView上。所以遍历view的子视图找到AView,执行AView的hitTest:withEvent:发现不符合条件(触摸点不再AView上)
3、因为AView已经不符合条件了,所以就不会再去遍历AView的子视图,最终确定控制器对应的view就是最合适的view,返回。

在AView的self.userInteractionEnabled设为NO或者 _aView.alpha = 0.001;
或者 _aView.hidden = YES;
的情况下
1、事件从上面一步一步传到AView,执行hitTest:withEvent:发现AView不符合要求(userInteractionEnabled为NO或者alpha<=0.01或者隐藏),同时不会再遍历其子视图。

总结:

view符合条件必须同时满足:

1、view的userInteractionEnabled为YES;
2、view的hidden为NO;
3、view的alpha大于0.01;

事件的传递:就是寻找最合适View的过程

1、当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中。
2、UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
3、AppDelegate 的 window 收到事件,并开始执行 hitTest:withEvent: ,发现符合要求,开始遍历子view.
4、View首先看自己是否能处理事件、是否隐藏、触摸点是否在自己身上、如果这三个条件都满足,那么继续遍历寻找子视图。
5、如果没有找到合适的子视图,则说明自己就是做合适的view。
7、如果有则执行4步骤。

事件传递到某视图后,就调用其hitTest:withEvent:方法寻找更合适的view。想让谁成为最合适的view就重写谁自己的父控件的hitTest:withEvent:方法返回指定的子控件。
return nil的意思是调用当前的view不是合适的view。
image.png
上图视图层级从外向里顺序依次是 CDA EFB 父视图。当点击界面发生触摸事件时,遍历父视图的子视图AB,从后往前遍历,先遍历A视图。

事件的响应:

1、首先看找到的最合适的view能否处理这个事件;
2、如果不能则会将事件传递给其父视图;
3、如果父视图仍然无法处理则会继续往上传递;一直传递到视图控制器;
4、判断视图控制器的根视图view是否能处理此事件:如果不能则接着判断该视图控制器能否处理此事件;
5、如果还是不能则继续向上传递;
6、一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃。

在事件的响应中,如果某个控件实现了touches...方法,则这个事件将由该控件来接受,如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….方法。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
// 1.自己先处理事件...
// 2.再调用系统的默认做法,再把事件交给上一个响应者处理
[super touchesBegan:touches withEvent:event]; 
}

事件的传递和响应的区别:

事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递(子控件到父控件)。

判断上一个响应者nextResponder

1、 当前view是控制器的view,那么控制器就是上一个响应者
2、 当前view不是控制器的view,那么父视图就是上一个响应者

应用:

在自定义的view中如果想在自己里面做页面push跳转,需要找到最上面的一个UINavigationController

#import "UIView+WGNavigationController.h"

@implementation UIView (WGNavigationController)

- (UINavigationController *)wgNavigationController{
    //响应连上一个响应者
    UIResponder *responder = self.nextResponder;
    while (responder) {
        if ([responder isKindOfClass:[UINavigationController class]]) {
            return (UINavigationController*)responder;
        }
        responder = responder.nextResponder;
    }
    return nil;
}

@end

参考文章:
1、史上最详细的iOS之事件的传递和响应机制-原理篇

2、iOS 响应链和事件传递

相关文章

网友评论

    本文标题:IOS事件的传递与响应研究

    本文链接:https://www.haomeiwen.com/subject/brvomftx.html