美文网首页
iOS 响应者链

iOS 响应者链

作者: 小乡123 | 来源:发表于2018-06-10 15:20 被阅读0次

    为了方便理解,会分为三步去解说, 1,点击事件找到对应的点击的视图的处理流程,2, 进行具体例子分析. 3, 常用的结论.

    一. 点击事件处理流程

    1. 当用户点击屏幕时,会产生一个触摸事件,系统会将该事件加入到一个由UIApplication管理的事件队列中

    2. UIApplication会从事件队列中取出最前面的事件进行分发以便处理,通常,先发送事件给应用程序的主窗口(UIWindow)

    3. 主窗口会首先调用pointInside:withEvent:, 该方法会根据触摸点来判断当前触摸是不是在当前视图内部, 并返回对应的标记:YES或NO

    4. 主窗口会调用hitTest:withEvent:

    4-1 如果第三步中的pointInside:withEvent:返回YES, 那么主窗口会它子视图的层级从上往下遍历, 然后子视图再调用自己的pointInside:withEvent:,

    4-1-1  如果子视图pointInside:withEvent:返回NO, 那么就会在调用hitTest:withEvent:中返回nil, 那么该子视图的父视图会按着视图层级接着往下遍历他的子视图, 继续执行4-1

    4-1-2 如果子视图pointInside:withEvent:返回YES,那么在调用hitTest:withEvent:中会返回当前的子视图, 则当前父视图认为已经找到了点击事件对应的视图, 就不再往下找了.

    5. 找到点击的视图, 则开始判断该视图是否能响应点击事件

    5-1 如果该视图处理了这个点击事件, 则执行该点击事件

    5-2 如果该视图没有处理点击事件, 则响应者链上寻找该视图的下一个响应者, 判断下一个响应者能否处理该点击事件, 依次类推, 直到找到能处理该点击事件的响应者, 然后响应点击事件

    5-3 如果该视图的响应者链上的所有对象都未处理该点击事件, 则丢弃该点击事件

    二. 下面根据具体的例子分析:

    下图中1, 2, 3 都是MainView的子视图, 4是1的子视图, 1,2,3的层级关系从上往下依次是3,2,1,视图3在MainView的最上层.

    下面分为几种情况:

    1. 点击MainView的空白区域

    1-1 首先会会调用到MainView的pointInside方法, 并返回YES, 这时会寻找子视图,并调用其pointInside

    1-2 调用MainView最上层的子视图3, 这个视图3的pointInside返回NO,然后调用视图3的hitTest,并返回nil

    1-3 调用MainView第二层子视图2, 视图2的pointInside返回NO, 然后调用视图2的hitTest并返回nil

    1-4 调用MainView最下层子视图1, 视图1的pointInside返回NO, 然后调用视图1的hitTest并返回nil

    1-5 MainView的所有子视图都寻找完成, 这时就调用到MainView的hitTest, 因为MainView所有的子视图hitTest都返回nil, 说明没点击到子视图, 这是MainView的hitTest会返回MainView实例对象, 这时,就找到了点击的视图MainView

    1-6 MainView 处理点击事件, 如果不能处理, 则交由MainView的下一级响应者处理, 依次类推, 如果一直到UIWindow都没有找到能响应该点击事件的响应者对象, 则丢弃该点击事件

    对应的伪代码逻辑如下:

    ==>MainView pointInside-->YES

    ==>视图3 pointInside-->NO     ==>视图3 hitTest-->nil

    ==>视图2 pointInside-->NO     ==>视图2 hitTest-->nil

    ==>视图1 pointInside-->NO     ==>视图1 hitTest-->nil

    ==>MainView hitTest-->MainView

    ==>MainView click

    2. 点击视图1中的橙色区域

    2-1 首先会会调用到MainView的pointInside方法, 并返回YES, 这时会寻找子视图,并调用其pointInside

    2-2 调用MainView最上层的子视图3, 这个视图3的pointInside返回NO,然后调用视图3的hitTest,并返回nil

    2-3 调用MainView第二层子视图2, 视图2的pointInside返回NO, 然后调用视图2的hitTest并返回nil

    2-4 调用MainView最下层子视图1, 视图1的pointInside返回YES, 然后视图1会寻找自己的所有子视图,这时找到了视图4, 然后视图4调用自己的pointInside方法, 返回NO, 接着视图4调用自己的hitTest方法并返回nil, 视图1调用自己的hitTest并返回视图1的对象

    2-5 MainView的所有子视图都寻找完成, 这时就调用到MainView的hitTest, 因为MainView所有的子视图1 hitTest都返回视图1对象, 说明没点击到子视图1, 这是MainView的hitTest会返回视图1的实例对象, 这时,就找到了点击的视图1

    2-6 视图1处理点击事件, 如果不能处理, 则交由视图1的下一级响应者MainView处理, 依次类推, 如果一直到UIWindow都没有找到能响应该点击事件的响应者对象, 则丢弃该点击事件

    对应的伪代码逻辑如下:

    ==>MainView pointInside-->YES

    ==>视图3 pointInside-->NO     ==>视图3 hitTest-->nil

    ==>视图2 pointInside-->NO     ==>视图2 hitTest-->nil

    ==>视图1 pointInside-->YES    ==>视图4 pointInside-->NO    ==>视图4 hitTest-->nil     ==>视图1 hitTest-->视图1

    ==>MainView hitTest-->视图1

    ==>视图1 click

    3. 点击视图2中的绿色区域

    3-1 首先会会调用到MainView的pointInside方法, 并返回YES, 这时会寻找子视图,并调用其pointInside

    3-2 调用MainView最上层的子视图3, 这个视图3的pointInside返回NO,然后调用视图3的hitTest,并返回nil

    3-3 调用MainView中间层的子视图2, 视图2的pointInside返回YES, 然后视图2会寻找自己的所有子视图,这时找不到其他子视图, 然后视图2调用自己的hitTest方法并返回视图2对象

    3-4 MainView就不在接着遍历其他子视图, 调用自己的好hitTest,并返回视图2对象, 这时,就找到了点击的视图2

    3-5 视图2处理点击事件, 如果不能处理, 则交由视图1的下一级响应者MainView处理, 依次类推, 如果一直到UIWindow都没有找到能响应该点击事件的响应者对象, 则丢弃该点击事件

    对应的伪代码逻辑如下:

    ==>MainView pointInside-->YES

    ==>视图3 pointInside-->NO     ==>视图3 hitTest-->nil

    ==>视图2 pointInside-->YES     ==>视图2 hitTest-->视图2

    ==>MainView hitTest-->视图2

    ==>视图2 click

    4. 点击视图3中的灰色区域

    4-1 首先会会调用到MainView的pointInside方法, 并返回YES, 这时会寻找子视图,并调用其pointInside

    4-2 调用MainView最上层的子视图3, 视图3的pointInside返回YES, 然后视图3会寻找自己的所有子视图,这时找不到其他子视图, 然后视图3调用自己的hitTest方法并返回视图2对象

    4-3 MainView就不在接着遍历其他子视图, 调用自己的好hitTest,并返回视图3对象, 这时,就找到了点击的视图3

    4-4 视图3处理点击事件, 如果不能处理, 则交由视图3的下一级响应者视图1处理, 依次类推, 如果一直到UIWindow都没有找到能响应该点击事件的响应者对象, 则丢弃该点击事件

    对应的伪代码逻辑如下:

    ==>MainView pointInside-->YES

    ==>视图3 pointInside-->YES     ==>视图3 hitTest-->视图3

    ==>MainView hitTest-->视图3

    ==>视图3 click

    5. 点击视图4和视图1的相交的蓝色区域

    5-1 首先会会调用到MainView的pointInside方法, 并返回YES, 这时会寻找子视图,并调用其pointInside

    5-2 调用MainView最上层的子视图3, 这个视图3的pointInside返回NO,然后调用视图3的hitTest,并返回nil

    5-3 调用MainView第二层子视图2, 视图2的pointInside返回NO, 然后调用视图2的hitTest并返回nil

    5-4 调用MainView最下层子视图1, 视图1的pointInside返回YES, 然后视图1会寻找自己的所有子视图,这时找到了视图4, 然后视图4调用自己的pointInside方法, 返回YES, 接着视图4遍历自己的子视图,没有子视图,则调用自己的hitTest方法并返回视图4对象, 然后其父视图视图1调用自己的hitTest并返回视图4的对象

    5-5 MainView的所有子视图都寻找完成, 这时就调用到MainView的hitTest, 因为MainView的子视图1 hitTest都返回视图4对象, 说明点击到子视图4, 这是MainView的hitTest会返回视图4的实例对象, 这时,就找到了点击的视图4

    5-6 视图4处理点击事件, 如果不能处理, 则交由视图1的下一级响应者视图1处理, 依次类推, 如果一直到UIWindow都没有找到能响应该点击事件的响应者对象, 则丢弃该点击事件

    对应的伪代码逻辑如下:

    ==>MainView pointInside-->YES

    ==>视图3 pointInside-->NO     ==>视图3 hitTest-->nil

    ==>视图2 pointInside-->NO     ==>视图2 hitTest-->nil

    ==>视图1 pointInside-->YES     ==>视图4 pointInside-->YES    ==>视图4 hitTest-->视图4      ==>视图1 hitTest-->视图4

    ==>MainView hitTest-->视图4

    ==>视图4 click

    6. 点击视图4和视图1的非相交的蓝色区域

    6-1 首先会会调用到MainView的pointInside方法, 并返回YES, 这时会寻找子视图,并调用其pointInside

    6-2 调用MainView最上层的子视图3, 这个视图3的pointInside返回NO,然后调用视图3的hitTest,并返回nil

    6-3 调用MainView第二层子视图2, 视图2的pointInside返回NO, 然后调用视图2的hitTest并返回nil

    6-4 调用MainView最下层子视图1, 视图1的pointInside返回NO, 然后视图1不会寻找自己的子视图,视图1调用自己的hitTest方法并返回nil

    6-5 MainView的所有子视图都寻找完成, 这时就调用到MainView的hitTest, 因为MainView所有的子视图hitTest都返回nil, 说明没点击到子视图, 这是MainView的hitTest会返回MainView实例对象, 这时,就找到了点击的视图MainView

    6-6MainView 处理点击事件, 如果不能处理, 则交由MainView的下一级响应者处理, 依次类推, 如果一直到UIWindow都没有找到能响应该点击事件的响应者对象, 则丢弃该点击事件

    对应的伪代码逻辑如下:

    ==>MainView pointInside-->YES

    ==>视图3 pointInside-->NO     ==>视图3 hitTest-->nil

    ==>视图2 pointInside-->NO     ==>视图2 hitTest-->nil

    ==>视图1 pointInside-->NO     ==>视图1 hitTest-->nil

    ==>MainView hitTest-->MainView

    ==>MainView click

    三. 一些结论

    1. 如果一个视图的userInteractionEnabled被设置为NO, 或者alpha<0.01, 那么该视图的pointInside:withEvent:方法不会执行, 该视图hitTest:withEvent:方法会执行, 但是会返回nil, 即该视图的 [super hitTest:point withEvent:event]方法调用会返回nil.

    2. 如果一个视图的backGroundColor被设置为clearColor, 那么该视图的触摸事件的处理不会受影响.

    3. 增大一个视图的触摸面积,只需要在该视图的pointInside:withEvent:方法中根据point属性处理,然后返回YES即可. 不需要重写hitTest:withEvent:方法.

    4. 如果视图超出它的父视图边界,还需要正常响应触摸事件, 需要重写hitTest:withEvent:方法, 在该方法中根据point属性处理,然后返回该视图即可.

    相关文章

      网友评论

          本文标题:iOS 响应者链

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