iOS之事件传递的具体过程介绍

一. hitTest:withEvent:调用过程

iOS系统检测到手指触摸(Touch)操作时会将其放入当前活动Application的事件队列,UIApplication会从事件队列中取出触摸事件并传递给key window(当前接收用户事件的窗口)处理,window对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,称之为hit-test view。

window对象会在首先在view hierarchy的顶级view上调用hitTest:withEvent:,此方法会在视图层级结构中的每个视图上调用pointInside:withEvent:,如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是hit-test view。

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

  1. 首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
  2. 若返回NO,则hitTest:withEvent:返回nil;
  3. 若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
  4. 若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
  5. 如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

hitTest:withEvent:方法忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。

对于每个触摸操作都会有一个UITouch对象,UITouch对象用来表示一个触摸操作,即一个手指在屏幕上按下、移动、离开的整个过程。UITouch对象在触摸操作的过程中在不断变化,所以在使用UITouch对象时,不能直接retain,而需要使用其他手段存储UITouch的内部信息。UITouch对象有一个view属性,表示此触摸操作初始发生所在的视图,即上面检测到的hit-test view,此属性在UITouch的生命周期不再改变,即使触摸操作后续移动到其他视图之上。

二.定制hitTest:withEvent:方法

如果父视图需要对对哪个子视图可以响应触摸事件做特殊控制,则可以重写hitTest:withEvent:pointInside:withEvent:方法。

这里有几个例子:

  1. hitTest Hacking the responder chain
    在此例子中button,scrollview同为topView的子视图,但scrollview覆盖在button之上,这样在在button上的触摸操作返回的hit-test view为scrollview,button无法响应,可以修改topView的hitTest:withEvent:方法如下:
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *result = [super hitTest:point withEvent:event];//父类调用hit方法,然后对每个子类进行判断,点击是否在这个子类上,然后子类再进行同样判断,直到找到点击的对象
        CGPoint buttonPoint = [underButton convertPoint:point fromView:self]
    //在整个self上进行坐标改变,变成以这个button的(0,0)为原点的坐标系,然后根据此基础,得出这个点得坐标
        if ([underButton pointInside:buttonPoint withEvent:event]) {
            return underButton;//如果这个点在under button的坐标系内
        }
        return result;
    }
    这样如果触摸点在button的范围内,返回hittestView为button,从button按钮可以响应点击事件。
    
    
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
        for (UIView *subView in self.subviews) {
    
          //坐标系的转换,以上面的各个子视图为坐标原点,然后得出各个坐标,convertPoint是点击的坐标
    
           CGPoint subViewPoint = [self convertPoint:point toView:subView];
    
          //得到的是相对于子视图,点击坐标的位置
    
          //判断是否在子视图范围内,因为坐标是以各个子视图为原点的,然后判断这个子视图是否在这个坐标里面,因为它是以子视图为原点的
    
           CGPoint ora = [self convertPoint:point toView:orange];//这部是得到点击的坐标位置,以orange为起始点,在self内;
    
           BOOL result = [subView pointInside:subViewPoint withEvent:event];//点击的点是否在子类里面
    
           if (result) {
    
               if (subViewPoint.x == ora.x && subViewPoint.y == ora.y) {
    
                  return orange;//通过判断点击的点与orange的x,y是否重和,point代表的二维坐标系中的点,如果与orang重合则返回orange
    
               }
    
    //           return subView;因为在这个上面如果返回子视图的话只有两个
    
               return [super hitTest:point withEvent:event];//触摸事件所需要处理的窗口。此方法会在视图层级结构中的每个视图上调用pointInside:withEvent:,如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是hit-test view父类调用这个方法
    
           }
    
        }
    
        return [super hitTest:point withEvent:event];
    
    }

     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值