iOS事件响应

如今我们但凡看到一块屏幕我们都会忍不住去点击,几乎每一块屏幕都能多点触控。我们用多点触控屏幕是那么自然,就像生来就有的技巧。那么在我们手指触碰屏幕的一瞬间,到底发生了什么呢?首先我们需要先了解事件类型。

1.事件类型

type
当我们操作手机时,一般有触摸屏幕、摇晃手机、远程遥控三种方式,分别对应的事件类型是:

    1.  触摸事件 (Multitouch events)

    2. 运动事件  (Accelerometer events)

    3. 远程控制  (Remote control events)

    当这些事件发生时,iOS会生成对应的响应链, 来查找第一响应对象并进行事件的分发,最后处理事件,完成相应操作。下面我们接着看关于响应链的概念。

2.响应链

    响应链,顾名思义,就是有一系列响应对象的集合成的一个层次结构。那什么又是响应对象呢?Cocoa里面规定:凡是继承于UIResponder或者UIResponder的子类的对象都可以作为响应对象,比如UIApplication、UIViewController和UIView。

    在响应用户触摸等事件中,APP具体会通过下面三步来完成操作:

    1.  生成事件。当用户点击屏幕时,会产生一个触摸事件,并放入由Application管理的事件队列中,然后在队列中取出最前面的事件交给Window处理。

    2.  查找第一响应对象。Window收到事件后会在视图层次结构中找到最适合的一个视图来处理事件,通常一个窗口中最适合处理当前事件的对象称为第一响应对象。

    3.  处理事件。通常最后是第一响应对象处理事件,如果第一响应对象无法处理事件,就会把事件传递给下一个响应对象,直到Application。如果Application也无法处理,那就丢弃掉此事件。

    在上述系列操作中,所参与到的UIApplication、UIViewController和UIView就作为响应对象构成这次事件的响应链。

2.1 查找第一响应对象

    当Window收到事件后,会用一种类似二分法的方式来查找第一响应对象,通常就是用户点击处最上层的一个View。

    Window实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回true,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。

    hitTest:withEvent:方法的处理流程如下:

    首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内,若返回false,则对应hitTest:withEvent:返回nil; 若返回true, 则向当前视图的所有子视图发送hitTest:withEvent:消息,直到有子视图返回非空对象或者全部子视图遍历完毕;若最后一层某个子视图pointInside:withEvent:方法返回true,则对应hitTest:withEvent:方法返回此对象,直到把此对象依次向上返回到Application则处理结束。

   我们结合一个实例来加深理解:

hit_testing
假设View A 是Window的根视图,用户点了View E之后:

  1. Window首先会对View A进行hit-test,具体表现为View A调用hitTest:withEvent:,而此方法进而会调用pointInside:withEvent:方法,显然返回true,并对View A所有的子视图(View B,View C)进行hit-test;

  2. View B调用pointInside:withEvent:方法,返回false,对应hitTest:withEvent:返回nil;

  3. View C调用pointInside:withEvent:方法,返回true,则对View C所有的子视图(View D,View E)进行hit-test;

  4. View D调用pointInside:withEvent:方法,返回false,对应hitTest:withEvent:返回nil;

  5. View E调用pointInside:withEvent:方法,返回true,而且View E没有子视图了,则hitTest:withEvent:返回View E,再往回溯,View C对应hitTest:withEvent:也返回View E,View A也返回View E,这样Application就知道了View E是第一响应对象。

2.1 处理事件
当Application就知道了第一响应对象后,就会把事件交给第一响应对象来处理,如果第一响应对象能顺利处理事件,则整个响应结束,但是第一响应对象如果无法处理事件,就会把事件传递给下一个响应对象(nextResponder),一直沿着响应链向上回溯。那么第一响应对象的下一响应对象是谁呢?我们结合下图进行解释:

nextResponder
左边为例:

  1. 如果接收到事件的初始View无法处理事件, 那么这个事件会交给他的SuperView, 因为他不是viewController等级中的最高级View。

  2. SuperView尝试处理事件,如果SuperView无法处理,则这个事件会交给他的SuperView,因为他不是viewController等级中的最高级View。

  3. 这样事件就传递到viewController等级中的最高级View,如果最高级View不能处理就会彻底给viewController。

  4. viewController尝试处理事件,如果无法处理就传递给Window,Window尝试处理,无法处理就传递给Application。

  5. Application尝试处理,如果无法处理就就丢弃该事件。

总之,当view无法处理事件时,如果是最高级view,并存在viewController,则传递给viewController,否则传递给SuperView,继续往上尝试处理事件。

view -> ViewController -> window -> Application -> 丢弃

  1. 注意事项

  2. 遍历查找最佳响应者时,从所有子视图的最上层view往下遍历(从subviews数组最后一个元素往前便利)。

  3. 遍历查找最佳响应者时,当一个子视图告诉OS没有被点击时,则它的子视图不会被检查(类似二分法)。

  4. 子视图在父视图边界外时,并且父亲的clipsToBounds属性为false时,子视图接受不到事件。

  5. 一个UIWindow对象在某一时刻只能有一个响应者对象可以成为第一响应者。

  6. 成为第一响应者必须要canBecomeFirstResponder,才能becomeFirstResponder。

  7. 手动设置某个view becomeFirstResponder时,当有事件发生时,该view不一定最先响应。比如点击button时会触发自身响应,而不管有无其他becomeFirstResponder的view。

  8. 第一响应者主要体现在,事件发生时没有响应者出来处理事件,这时候第一响应者就会尝试处理事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值