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

本文详细解析了iOS中触控事件的处理流程,包括事件对象UIEvent的生成、事件分发机制、Hit-Testing原理及其实现过程。通过具体实例说明了如何确定最合适的视图来响应触摸事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

每当我们点击了一下iOS设备的屏幕,UIKit就会生成一个事件对象UIEvent,然后会把这个Event分发给当前activeapp(官方原文说: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上,如果是的话,就会去遍历这个Viewsubviews,直到找到最小的能够处理事件的view,如果整了一圈没找到能够处理的view,则返回自身。来一个简单的图说明一下。


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

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

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、付费专栏及课程。

余额充值