事件分发机制Hit-Test View的应用(TabBar凸起不能点击解决方法)
一、事件分发原理
--- hitTest:withEvent:方法大致处理流程是这样的: 首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内: -- 若pointInside:withEvent:方法返回NO,说明触摸点不在当前视图内,则当前视图的hitTest:withEvent:返回nil -- 若pointInside:withEvent:方法返回YES,说明触摸点在当前视图内,则遍历当前视图的所有子视图(subviews),调用子视图的hitTest:withEvent:方法重复前面的步骤,子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图的hitTest:withEvent:方法返回非空对象或者全部子视图遍历完毕: -- 若第一次有子视图的hitTest:withEvent:方法返回非空对象,则当前视图的hitTest:withEvent:方法就返回此对象,处理结束 -- 若所有子视图的hitTest:withEvent:方法都返回nil,则当前视图的hitTest:withEvent:方法返回当前视图自身(self) -- 最终,这个触摸事件交给主窗口的hitTest:withEvent:方法返回的视图对象去处理
• hitTest:withEvent:方法会忽略以下视图:
1> 隐藏(hidden=YES)的视图
2> 禁止用户操作(userInteractionEnabled=NO)的视图
3> alpha<0.01的视图
4> 如果一个子视图的区域超过父视图的区域(如果父视图的clipsToBounds属性为NO,超过父视图区域的子视图内容也会显示),那么正常情况下在父 视图区域外的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种
综上所述可得:如果父视图的userInteractionEnabled=NO,触摸事件不会继续往下传递给子视图,所以子视图永远无法处理触摸事件。而UIImageView在默认情况下的userInteractionEnabled就是NO。
二、官方详细说明
假设我们现在点击到了图中的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 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,你点击超出的部分,是不会处理你的事件的,就是这么任性!
三、事例
1、解决TabBar凸起部分不能点击
//重写hitTest方法,去监听发布按钮的点击,目的是为了让凸出的部分点击也有反应 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ //这一个判断是关键,当页面不显示TabBar,这个按钮还是有反应,这样就不好了 //self.isHidden == NO 说明当前页面是有tabbar的,那么肯定是在导航控制器的根控制器页面 //在导航控制器根控制器页面,那么我们就需要判断手指点击的位置是否在发布按钮身上 //是的话让发布按钮自己处理点击事件,不是的话让系统去处理点击事件就可以了 if (self.hidden == NO) { //将当前tabbar的触摸点转换坐标系,转换到发布按钮的身上,生成一个新的点 CGPoint newP = [self convertPoint:point toView:self.publicButton]; //判断如果这个新的点是在发布按钮身上,那么处理点击事件最合适的view就是发布按钮 if ([self.publicButton pointInside:newP withEvent:event]) { return self.publicButton; }else{//如果点不在发布按钮身上,直接让系统处理就可以了 return [super hitTest:point withEvent:event]; } } return [super hitTest:point withEvent:event]; }
将来的自己,会感谢现在不放弃的自己!