iOS-hitTest:withEvent与自定义hit-testing规则
2016-01-09 10:45:07 | 来源:玩转帮会 | 投稿:佚名 | 编辑:小柯

原标题:iOS-hitTest:withEvent与自定义hit-testing规则

在做tableView嵌套scrollView的时候怕手势冲突,研究了一下hitTest,虽然最后没用上,但是觉得比较有用,写了一个DEMO,通过重写hitTest:withEvent,实现了超出父视图范围响应触摸事件等自定义hit-testing规则,我的理解还很粗浅,如果有错误或者更优解,欢迎大家指出,我看到后会立即修正~

DEMO:https://github.com/liulishuo/LLSHitTestView

预备知识(M了个J、iOS developer library)

对于触摸事件的响应,首先要找到能够响应该事件的对象,iOS是用hit-testing 来找到哪个视图被触摸了(hit-test view),也就是以keyWindow为起点,hit-test view为终点,逐级调用hitTest:withEvent。

MJ大神的图

  • 测试用例:在每个视图类的hitTest:withEvent:打印两次log:1.调用时 2.返回值时

打印log的位置

触摸view2

线索log

hitTest:withEvent:调用顺序:…->base->view2->view3

hitTest:withEvent:返回顺序: view3(nil) -> view2(self) -> base(view2)->…

触摸view1

屏幕快照 2015-12-03 09.52.39.png

hitTest:withEvent:调用顺序:…->base->view2(nil)-> base->view1

hitTest:withEvent:返回顺序: view2(nil)->base, view1(self)->base(view1)->…

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

  • 先调用pointInside:withEvent:判断触摸点是否在当前视图内

1.如果返回YES,那么该视图的所有子视图调用hitTest:withEvent,调用顺序由层级低到高(top->bottom)依次调用。

2.如果返回NO,那么hitTest:withEvent返回nil,该视图的所有子视图的分支全部被忽略

  • 如果某视图的pointInside:withEvent:返回YES,并且他的所有子视图hitTest:withEvent:都返回nil,或者该视图没有子视图,那么该视图的hitTest:withEvent:返回自己。

  • 如果子视图的hitTest:withEvent:返回非空对象,那么当前视图的hitTest:withEvent:也返回这个对象,也就是沿原路回推,最终将hit-test view传递给keyWindow

  • 以下视图的hitTest:withEvent:方法会返回nil,导致自身和其所有子视图不能被hit-testing发现,无法响应触摸事件:

1.隐藏(hidden=YES)的视图

2.禁止用户操作(userInteractionEnabled=NO)的视图

3.alpha<0.01的视图

4.视图超出父视图的区域

思路

既然系统通过hitTest:withEvent:做传递链取回hit-test view,那么我们可以在其中一环修改传递回的对象,从而改变正常的事件响应链。

实现

  • 强制指定某视图响应触摸事件:

将截获的对象替换成指定的对象,可以随便替换,只要在替换时你能拿到要替换的对象的实例。穿透scrollView点击scrollView后面的button就是这样做的。可以试试换成一个(hidden=YES、userInteractionEnabled=NO、alpha<0.01)的对象,比较违反直觉,被隐藏\禁用手势的视图一样能响应触摸事件。

经测试,将返回的hit-test view替换为加了手势的view,该view hidden=YES、userInteractionEnabled=NO、alpha<0.01三种情况都可响应事件,但是如果替换为button,并且button的userInteractionEnabled=NO或者enable=NO那么无法响应事件。

  • 忽略指定的视图:

在hitTest:withEvent:里筛选返回值,针对指定的对象返回nil

if([viewisEqual:XXX])
{
returnnil;
}

这样做的好处是不会阻断hit-testing检测,既可忽略指定的视图又不会屏蔽其子视图。

  • 定制触摸事件的响应范围

在hitTest:withEvent:里筛选point,判断point在不在指定的范围内

if(_path)
{
if(!CGPathContainsPoint(_path.CGPath,NULL,point,NO))
{
returnnil;
}
}

_path 是一段bezier曲线,详见代码。

  • 超出父视图范围响应

选定一个节点,遍历他的所有子节点用pointInside:withEvent:判断是否命中,直到找到命中的最低层级的视图,此时我们已经抛弃了系统的hit-testing规则。

-(UIView*)getTargetView:(UIView*)view
point:(CGPoint)point
event:(UIEvent*)event
{
__blockUIView*subView;
//逆序由层级最低也就是最上层的子视图开始
[view.subviewsenumerateObjectsWithOptions:NSEnumerationReverseusingBlock:^(__kindofUIView*_Nonnullobj,NSUIntegeridx,BOOL*_Nonnullstop){
//point从view转到obj中
CGPointhitPoint=[objconvertPoint:pointfromView:view];
//NSLog(@"%@-%@",NSStringFromCGPoint(point),NSStringFromCGPoint(hitPoint));
if([objpointInside:hitPointwithEvent:event])//在当前视图范围内
{
if(obj.subviews.count!=0)
{
//如果有子视图递归
subView=[selfgetTargetView:objpoint:hitPointevent:event];
if(!subView)
{
//如果没找到提交当前视图
subView=obj;
}
}
else
{
subView=obj;
}
*stop=YES;
}
else//不在当前视图范围内
{
if(obj.subviews.count!=0)
{
//如果有子视图递归
subView=[selfgetTargetView:objpoint:hitPointevent:event];
}
}
}];
returnsubView;
}

LLSHitTestView

层级关系

我们在层级比较高的view1使用自定义的hit-testing规则,其上的2、3、4,无论是否超出边界,均能正常响应点击事件,详见代码。

问题:为什么一次触摸会触发两次hitTest:withEvent:?

tags:

上一篇  下一篇

相关:

苹果搜索联想词又成ASO风口,展现逻辑到底如何?

前天有一篇关于搜索联想词分享在圈内激起千层浪,一度引起全民热议,不到一天时间竟有上百位朋友向我咨询操

十分钟掌握SQLite操作

最近用Ruby写了一个七牛的demo参赛作品,使用了sqlite3,用到很多操作,利用假期的时间,简单做一个快速掌握

源码推荐(01.07B):MVVM登录页面,一句代码设置UILabel的行距间距

MVVM登录页面(上传者:1060545231)用 ReactiveCocoa框架实现的 MVVM 构架,讲业务逻辑更多的写到 ViewModel

戴维斯又伤了 这次飞进观众席

安东尼戴维斯一边摸着背部,一边黯然走出球场接受治疗。(美联社) 鹈鹕明星前锋安东尼戴维斯本季真的灾难连

嘎嘎性能力超猛?遭打脸15公分不持久

嘎嘎据传下面「15公分」床上功夫一流,却遭打脸不持久。(图/本报系资料照) 「MP魔幻力量」主唱嘎嘎丑闻

拥有这10件单品 分分钟成型男


你或许为每日的穿着发愁很多年了?!要想成为时尚型男,冬季必备这10样单品,即便离穿衣品位大师还

佐纳利男装第三代店铺形象:2016时尚与精彩继续


经过一个多月的精心准备和筹划,ZENL佐纳利风尚男装以全新的第三代形象,于2016年元月6日在佛山南海

诺威兹基生涯1300场出赛 史上第16

小牛球星诺维兹基生涯正式出赛1300场。(美联社资料照) 小牛「德国坦克」诺威兹基今天达到个人生涯另一里程

热烈祝贺【EXUN时尚C°】贵州铜仁1月8日盛大开业


正因为大家的需求,所以才会多成千上万的店铺营业,满足于每一位顾客。而衣讯品牌女装越来越受到广

NEW 新品1A ‖ 活力悠游 · 玩味春意


春意来袭,迈开步伐来一次惬意之旅,边工作边旅行,感受心灵与身心完美超脱。摆脱单调空间,融入自

站长推荐: