iOS hitTest解决超出父视图范围并响应事件

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

point       : 在接收器的局部坐标系(界)中指定的点。
event       : 系统保证调用此方法的事件。如果从事件处理代码外部调用此方法,则可以指定nil。
returnValue : 视图对象是当前视图和包含点的最远的后代。如果点完全位于接收方的视图层次结构之外,则返回nil。

该方法的作用 在于 : 在视图的层次结构中寻找一个最适合的 view 来响应触摸事件。

该方法会被系统调用,调用的时候,如果返回为nil,即事件有可能被丢弃,否则返回最合适的view 来响应事件。

 

事件的传递和响应的区别:

事件的传递是从上到下(父控件到子控件),事件的响应(调用)是从下到上(顺着响应者链条向上传递:子控件到父控件。

二、hitTest 的响应(调用)顺序

view -> superView ...- > UIViewController.view -> UIViewController -> UIWindow -> UIApplication -> 事件丢弃

三、事件的传递顺序

touch -> UIApplication -> UIWindow -> UIViewController.view -> subViews -> ....-> 合适的view

 

 

四、hitTest的实现思路

常见的视图不响应事件不外乎如下几种情况

1、view.userInteractionEnabled = NO;

2、view.hidden = YES;

3、view.alpha < 0.05

4、view 超出 superview 的 bounds

那么hitTest 就可根据上面 结果 大概模拟下 hitTest 方法的大概实现

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{

    // 如果交互未打开,或者透明度小于0.05 或者 视图被隐藏
    if (self.userInteractionEnabled == NO || self.alpha < 0.05 || self.hidden == YES)
    {

        return nil;
    }

    // 如果 touch 的point 在 self 的bounds 内
    if ([self pointInside:point withEvent:event])
    {

        for (UIView *subView in self.subviews)
        {

            //进行坐标转化
            CGPoint coverPoint = [subView convertPoint:point fromView:self];

           // 调用子视图的 hitTest 重复上面的步骤。找到了,返回hitTest view ,没找到返回有自身处理
            UIView *hitTestView = [subView hitTest:coverPoint withEvent:event];

            if (hitTestView)
            {

                return hitTestView;
            }
        }

        return self;


    }

    return nil;

}
 

 

 

五、hitTest的运用场景

1、事件穿透

示意图

青色的遮罩层(maskView) 在黄色 按钮(btn) 的上层,即 视图的添加顺序为,先添加 黄色 按钮(btn),再添加 青色的遮罩(maskView)。 有这么个需求,点击按钮修改maskView 的背景颜色。

注意 : btn 和 maskview 有重叠部分。视图都添加到UIViewController.view 。

1>、首先,调用UIViewController.view的hitTest。

2>、其次,遍历子视图,进行坐标转化,判断 point 是否在 bounds 内,发现 maskview 和 btn 都满足

3>、再者,调用maskview 和 btn 的hitTest 方法,到这里,我们的目标的hitText View 很显然是btn,那么我们自然就很容易想到,根据 maskview 的isa 找到 类对象,在类对象 重写 hitTest 方法,当hitTestview == self ,返回nil 即可。这样,事件就别 btn 捕获到。代码如下:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{

    UIView *hitTestView = [super hitTest:point withEvent:event];

    if (hitTestView == self) 
    {

        return nil;
    }else
    {

        return hitTestView;
    }

}
 

 

2、子视图超出父视图 范围

如图所示 


发布按钮已然已经超出tabbar的范围,那么该按钮是如何响应点击事件的?

要让中间按钮响应点击超出TabBar按钮部分的点击事件,则需要重写TabBar的hitTest方法了,在执行hitTest方法时,判断点击区域在中间按钮的区域,则返回中间按钮,响应该事件,代码如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {


     //将当前tabbar的触摸点转换坐标系,转换到中间按钮的身上,生成一个新的点
     CGPoint newP = [self convertPoint:point toView:self.centerBtn];

      //判断如果这个新的点是在中间按钮身上,那么处理点击事件最合适的view就是中间按钮
      if ( [self.centerBtn pointInside:newP withEvent:event]) 
      {
            return self.centerBtn;
       }


    return [super hitTest:point withEvent:event];

}//重写hitTest方法,去监听中间按钮的点击,目的是为了让凸出的部分点击也有反应
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值