iOS开发之再议事件的产生和传递

事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的 view 、寻找最合适的 view 的底层实现、拦截事件的处理) ->  找到最合适的 view 后事件的处理( touches 方法的重写,也就是事件的响应)

iOS 中的事件可以分为 3 大类型:
1. 触摸事件
2. 加速计事件
3. 远程控制事件

每当我们点击了一下 iOS 设备的屏幕, UIKit 就会生成一个事件对象 UIEvent ,然后会把这个 Event 分发给当前 active app( 官方原文说: Then it places the event object in the active app’s event queue.)

告知当前活动的 app 有事件之后, UIApplication 单例就会从事件队列中去取最新的事件,然后分发给能够处理该事件的对象。 UIApplication 获取到 Event 之后, Application 就纠结于到底要把这个事件传递给谁,这时候就要依靠 HitTest 来决定了。

iOS 中, hit-Testing 的作用就是找出这个触摸点下面的 View 是什么, HitTest 会检测这个点击的点是不是发生在这个 View 上,如果是的话,就会去遍历这个 View subviews ,直到找到最小的能够处理事件的 view ,如果整了一圈没找到能够处理的 view ,则返回自身。来一个简单的图说明一下。


假设我们现在点击到了图中的 E hit-testing 将进行如下步骤的检测 ( 不包含重写 hit-test 并且返回非默认 View 的情况 )
1 、触摸点在 ViewA 内,所以检查 ViewA Subview B C
2 、触摸点不在 ViewB 内,触摸点在 ViewC 内部,所以检查 ViewC Subview D E
3 、触摸点不在 ViewD 内,触摸点发生在 ViewE 内部,并且 ViewE 没有 subview ,所以 ViewE 属于 ViewA 中包含这个点的最小单位,所以 ViewE 变成了该次触摸事件的 hit-TestView

PS.
1 、默认的 hit-testing 顺序是按照 UIView Subviews 的逆顺序
2 、如果 View 的同级别 Subview 中有重叠的部分,则优先检查顶部的 Subview ,如果顶部的 Subview 返回 nil 再检查底部的 Subview
3 Hit-Test 也是比较聪明的,检测过程中有这么一点,就是说如果点击没有发生在某 View 中,那么该事件就不可能发生在 View Subview 中,所以检测过程中发现该事件不在 ViewB 内,也直接就不会检测在不在 ViewF 内。也就是说,如果你的 Subview 设置了 clipsToBounds= NO , 实际显示区域可能超出了 superView frame ,你点击超出的部分,是不会处理你的事件的,就是这么任性!

Hit-Test
的检查机制如上所示,当确定了 Hit-TestView 时,如果当前的 application 没有忽略触摸事件 (UIApplication:isIgnoringInteractionEvents), application 就会去分发事件 (sendEvent:->keywindow:sendEvent:)

UIView
中提供两个方法用来确定 hit-testing View, 如下所示
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
当一个 View 收到 hitTest 消息时,会调用自己的 pointInside:withEvent: 方法 , 如果 pointInside 返回 YES ,则表明触摸事件发生在我自己内部,则会遍历自己的所有 Subview 去寻找最小单位 ( 没有任何子 view) UIView ,如果当前 View.userInteractionEnabled = NO ,enabled= NO (UIControl), 或者 alpha<= 0.01 , hidden 等情况的时候, hitTest 就不会调用自己的 pointInside 了,直接返回 nil ,然后系统就回去遍历兄弟节点。简而言之,可以写成这样
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden)
    {
        return nil;
    }
    BOOL inside = [self pointInside:point withEvent:event];
    UIView *hitView = nil;
    if (inside) {
        NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
        for (UIView *subview in enumerator) {
            hitView = [subview hitTest:point withEvent:event];
            if (hitView) {
                break;
            }
        }
        if (!hitView)
        {
            hitView = self;
        }
        return hitView;
    } else {
        return nil;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值