UIResponder事件响应链学习笔记

1、什么是响应链?

大多数事件的分发都是依赖响应链的。响应链是由一系列连接在一块儿的响应者(UIResponse子类:UIApplicationUIViewControllerUIView)组成的。通常状况下,一条响应链开始于第一响应者,结束于application对象。若是一个响应者不能处理事件,则会将事件沿着响应链传到下一响应者。ios

@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
复制代码

2、事件的传递与响应

2.一、事件的传递:寻找事件的第一响应者(Hit-Testing)

事件被苹果分为3种大类型: 触摸事件加速计事件以及远程遥控事件git

当一个事件发生后,事件会从父控件传给子控件,也就是说由github

硬件 -> 系统 -> UIApplication -> UIWindow -> SuperView -> SubViewbash

以上就是事件的传递,也就是寻找第一响应者的过程。 符合第一响应者的条件包括:app

  • touch事件的位置在响应者区域内 pointInside:withEvent: == YES
  • 响应者 self.hidden != NO
  • 响应者 self.alpha > 0.01
  • 响应者 self.userInteractionEnabled = YES
  • 遍历 subview 时,是从上往下顺序遍历的,即 view.subviews 的 lastObject 到 firstObject 的顺序,找到合适的响应者view,即中止遍历.

第一响应者对于接收到的事件有3种操做:ide

  • 不拦截,默认操做。事件会自动沿着默认的响应链往下传递
  • 拦截,再也不往下分发事件。重写 touchesBegan:withEvent: 进行事件处理,不调用父类的 touchesBegan:withEvent:
  • 拦截,继续往下分发事件。重写 touchesBegan:withEvent: 进行事件处理,同时调用父类的 touchesBegan:withEvent: 将事件往下传递

下图展现了Hit-Testing的逻辑ui

2.二、事件的响应:一旦事件的第一响应者肯定了,这个事件所处的响应链就肯定了

案例一:下图是官网对于响应链的示例展现

  • 图中虚线箭头是指若该UIView是做为UIViewController根视图存在的,则其nextResponderUIViewController对象;
  • 如果直接add在UIWindow上的,则其nextResponder为UIWindow对象。
// 若触摸发生在UITextField上,则事件的传递顺序是:
UITextField ——> UIView ——> UIView ——> UIViewController ——> UIWindow ——> UIApplication ——> UIApplicationDelegation
复制代码

案例二:参考下图

  • 一、 首先由 view 来尝试处理事件,若是他处理不了,事件将被传递到他的父视图 superview
  • 二、superview 也尝试来处理事件,若是他处理不了,继续传递他的父视图 UIViewcontroller.view
  • 三、UIViewController.view 尝试来处理该事件,若是处理不了,将把该事件传递给 UIViewController
  • 四、UIViewController 尝试处理该事件,若是处理不了,将把该事件传递给主窗口 Window
  • 五、主窗口 Window 尝试来处理该事件,若是处理不了,将传递给应用单例 Application
  • 六、若是 Application 也处理不了,则该事件将会被丢弃

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

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

如何判断上一个响应者?

若是当前这个view是控制器的view,那么控制器就是上一个响应者 若是当前这个view不是控制器的view,那么父控件就是上一个响应者spa

响应者链条的事件传递过程?

若是view 的控制器存在,就传递给控制器;若是控制器不存在,则将其传递给它的父视图 在视图层次结构的最顶级视图,若是也不能处理收到的事件或消息,则其将事件或消息传递给 window 对象进行处理 若是 window 对象也不处理,则其将事件或消息传递给 UIApplication 对象 若是 UIApplication 也不能处理该事件或消息,则将其丢弃(销毁)code

如何作到一个事件多个对象处理?

由于系统默认作法是把事件上抛给父控件,因此能够经过重写本身的touches方法和父控件的touches方法来达到一个事件多个对象处理的目的。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
    // 1.本身先处理事件...
    NSLog(@"do somthing...");
    // 2.再调用系统的默认作法,再把事件交给上一个响应者处理
    [super touchesBegan:touches withEvent:event]; 
}
复制代码

事件的生命周期

一、系统响应阶段

二、APP响应阶段

应用场景:

  • 一、 重写子view的point:inside`` → 扩大Button的点击区域(上下左右各增长20)
  • 二、 重写父view的point:insde`` →子view超出了父view的bounds响应事件
  • 三、 若是一个Button被一个View盖住了,在触摸View时,但愿该Button可以响应事件
  • 四、 特殊的UIScrollView
  • 五、 利用响应链传递自定义UI事件

总结

  • 一、若是父控件不能接收触摸事件,则子控件也没法接收触摸事件
  • 二、若是想让控件不处理触摸事件,能够设置userInteractionEnabled = NO,结果是包括父控件在内的全部子控件都不能处理触摸事件(虽然设置透明度和hidden=YES也能够,可是那样就看不见了注意:若是父控件的透明度设置为0或者hidden=YES,那么子控件也是不可见的。)
  • 三、遍历一个控件的子控件的顺序是从上到下的(最后添加的view最早被遍历)。
  • 四、指定某一个子控件响应事件,只须要在父控件的hitTest中返回指定的子控件就能够。
  • 五、若是一个控件的isUserInteractionEnabled=false,想让它继续继续处理触摸事件,能够在它的父控件的hitTest方法中直接返回它。
  • 六、hitTest查找第一响应者的时候,即使父控件是第一响应者,仍是要调用子控件的hitTest方法,不然怎么知道是否是还有其余最合适的响应者
  • 七、
    • → 一、先调用父控件的point:inside:方法
    • → 二、调用最上面子控件的point:inside:方法
    • → 三、若是最上面子控件的point:inside:方法返回false,则对应的hitTest返回nil
    • → 四、若是最上面子控件的point:inside:方法返回true,则调用对应的hitTest方法重复上面的操做返回子控件的最合适子控件

疑问?

  • UIGestureRecongnizerUIContorl均可以处理触摸事件
    • UIGestureRecognizer:使用addGestureRecognizer方法处理事件
    • UIControl:使用addTarget方法处理事件
    • UIResponder:使用touches等一系列方法处理事件

UIButton继承自UIControlUIControl继承自UIView,若是给UIButton添加了手势,并实现了本身的处理事件的>>方法,当点击UIButton的时候发现touches方法走了,手势方法(addGestureRecognizer)也走了,本身的方法(addTarget)没有走。

由此能够得出一个结论:UIGestureRecognizer的优先级 > >UIContol的优先级,当一个UIButton即实现了本身的方法(addTarget),又添加了手势方法(addGestureRecognizer)的话,本身的方法(addTarget)会被屏蔽掉,不论是否添加了手势,touches方法都会处理。

参考连接:

Hit-Testing in iOS

iOS触摸事件全家桶

iOS事件处理之Hit-Testing

iOS 事件的传递响应机制

iOS事件响应链中Hit-Test View的应用

UIView的hitTest和pointInside方法

相关文章
相关标签/搜索