将来跳槽用

iOS-触摸事件传递、事件响应者链

2018-05-21  本文已影响56人  梦蕊dream

前言,本文简单了解触摸事件传递和事件响应者链。

一、知识点简介

1.1 iOS中的事件介绍

iOS中的事件可以分为3大类型:

iOS处理触屏事件,分为两种方式:

1.2 UIGestureRecognizer

UIKit中我们常用的是UIControl类实例的addTarget:action:forControlEvents:方法维护控件目标行为表,除了UIKit控件外,手势识别器UIGestureRecognizer类的实例也可以处理触屏事件,其内部也使用目标行为表。

UIKit内置了6种手势识别器:
UITapGestureRecognizer:点击(单击、双击、三连击等)手势。
UIPinchGestureRecognizer:缩放手势。
UIPanGestureRecognizer:拖拽手势。
UISwipeGestureRecognizer:滑动手势。
UIRotationGestureRecognizer:旋转手势。
UILongPressGestureRecognizer:长按手势。

1.3 UITouch

当你用一根手指触摸屏幕时, 会创建一个与之关联的UITouch对象, 一个UITouch对象对应一根手指. 在事件中可以根据NSSet中UITouch对象的数量得出此次触摸事件是单指触摸还是双指多指等等.

触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView   *view;
短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger      tapCount;
记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval  timestamp;
当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase    phase;
// UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件
// 一根或者多根手指开始触摸view,系统会自动调用view的下面方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
// 一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
// 一根或者多根手指离开view,系统会自动调用view的下面方法
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
// 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
// 提示:touches中存放的都是UITouch对象

1.4 UIEvent

每产生一个事件, 就对应产生一个UIEvent. UIEvent记录着该事件产生的时间, 事件的类型等等.

UIEvent几个重要的属性 :
事件类型
@property(nonatomic,readonly) UIEventType     type;
@property(nonatomic,readonly) UIEventSubtype  subtype;
事件产生的时间
@property(nonatomic,readonly) NSTimeInterval  timestamp;

1.5 响应者对象(UIResponder)

在iOS中不是任何对象都能处理事件, 只有继承了UIResponder的对象才能接收并处理事件,我们称为响应者对象.
UIApplication,UIViewController,UIView都继承自UIResponder,因此他们都是响应者对象, 都能够接收并处理事件.

继承自UIResponder的类能处理事件是由于UIResponder内部提供了以下方法:

触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;

两个UIView相关属性:

二、事件传递

注:一般事件的传递是从父控件传递到子控件的;如果父控件接受不到触摸事件,那么子控件就不可能接收到触摸事件。

示例 View

例如:

2.1 查找合适 View 步骤

应用查找到最合适的控件来处理事件,有以下准则

详细步骤:

判断UIView不能接收触摸事件的三种情况:

2.2 hitTest:withEvent:方法

只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法寻找合适的View.
作用:
寻找并返回最合适的view(能够响应事件的那个最合适的view)
不管这个控件能不能处理事件,也不管触摸点在不在这个控件上,事件都会先传递给这个控件,随后再调用hitTest:withEvent:方法
判断当前View 是否满足接收触摸事件的条件(1.用户交互 2. 隐藏 3. 透明度)

底层具体实现如下 :
- (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,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的,判断所有子控件不合适最后返回父控件。

2.3 pointInside:withEvent:方法

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
该方法判断触摸点是否在控件身上, 是则返回YES, 否则返回NO

搭配hitTest:withEvent:达到目的:

2.4 视图不响应

发现视图无法响应点击事件,可以检查下面几项:

1、hidden = YES 视图被隐藏
2、userInteractionEnabled = NO 不接受响应事件
3、alpha <= 0.01,透明视图不接收响应事件
4、子视图超出父视图范围
5、需响应视图被其他视图盖住
6、是否重写了其父视图以及自身的hitTest方法
7、是否重写了其父视图以及自身的pointInside方法

三、事件响应

找到合适的View之后就会调用该view的touches方法要进行响应处理具体的事件,找不到最合适的view,就不会调用touches方法进行事件处理。
响应者链条其实就是很多响应者对象(继承自UIResponder的对象)一起组合起来的链条称之为响应者链条。
一般默认做法是控件将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理 (即调用super的touches方法)。

判断当前响应者的上一个响应者是谁:

3.1 响应链

响应链是“事件派发”的原则和规定,那么响应链是什么?顾名思义事件链是一个链条,详细的定义如下:

3.2 touch 响应

如果控制器不响应响应touches方法,就交给UIWindow。如果UIWindow也不响应,交给UIApplication,如果都不响应事件就作废了。

//只要点击控件,就会调用touchBegin,如果没有重写这个方法,自己处理不了触摸事件
// 上一个响应者可能是父控件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
// 默认会把事件传递给上一个响应者,上一个响应者是父控件,交给父控件处理
[super touchesBegan:touches withEvent:event]; 
// 注意不是调用父控件的touches方法,而是调用父类的touches方法
// super是父类 superview是父控件 
}

3.3 触摸事件传递响应过程:

完整的触摸事件的传递响应过程为:
UIApplication–>UIWindow–>递归找到最合适处理的控件–>控件调用touches方法–>判断是否实现touches方法–>没有实现默认会将事件传递给上一个响应者–>找到上一个响应者–>找不到方法作废
总结:触摸或者点击一个控件,然后这个事件会从上向下(从父->子)找最合适的view处理,找到这个view之后看他能不能处理,能就处理,不能就按照事件响应链向上(从子->父)传递给父控件

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

上一篇下一篇

猜你喜欢

热点阅读