一个99%刚入职场的iOSer踩过的坑
小胡刚入职的第二天,iOS组长给他一个实习任务:用UICollectionView搭一个商品选择界面。小胡觉着这不难啊,估摸着半小时就能搭完页面,调试调试,对接下接口,半天也完成了。
故事总是这样,从喜剧开始,最终变成了一部悬疑剧:
大半天过去了,小胡惴惴不安地找到组长:“我遇到一个诡异的问题...”
组长:“什么问题?”
“UICollectionView的didSelectItemAtIndexPath怎么点都不响应!”
“哦?我来看看”,说着,组长打开“视图层次查看器”,结果发现,UICollectionView 被加到一个全屏的UIImageView背景上,而UIImageView的userInteractionEnabled默认为NO !!!
“这样你当然什么触摸事件都接收不到了呀!”
小胡羞的满脸通红,感觉这个失误和没插电源,却说电脑坏了一样愚蠢,不过他还是说出了心中的疑问:组长,我明明点击的是UICollectionView,为什么触摸事件却被父视图给屏蔽了呢?
组长看着小胡,“看来,得给你理一理响应者链了”:
响应者链有两个过程
响应者链是iOS处理“事件”的一种机制,我们可以简单理解为触摸事件在类中传递的过程,而构成iOS界面上的几乎所有类,UIApplication、UIView、UIViewController 都是直接继承自UIResponder,所以当用户点击屏幕后,由哪个UIResponder来处理这个点击,就需要进行一番选择。
一个屏幕上,UIResponder 被组织成了一棵树:
触摸事件由根结点逐层往下传递,这是第一个传递过程,叫做
命中测试
,是由hitTest 方法完成的:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
目的是为了找到点击发生在哪个UIResponder的范围内,即所谓的“命中者”。
覆盖 hitTest 时,要调用
[super hitTest:point withEvent:event]
,否则链条被中断,子视图将不会执行hitTest,也就无法参与到响应者链中来。
找到命中者后,就要在这些命中者中找个“响应者”,响应者是有“应聘条件”的:
- userInteractionEnabled不能为NO,注意,userInteractionEnabled 具有传递作用,即父视图为NO,则子视图的 hitTest 都通不过。
- hidden 必须为NO,即不能被隐藏。
- alpha不能低于 0.01,也就是不能几乎透明。
这个过程是与第一个过程相反的,由子视图
向上传递:
第一个满足以上条件的命中者,如果实现
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
就被确认为响应者,系统认为事件被正确的处理了,事件传递过程到此结束
。
命中者中找不到响应者,怎么办?事件就会被一直传递,直到遍历了UIResponder 树上全部结点,最后由UIWindow做最后处理。
响应者链作用
- 可以让一个触摸事件发生的时候让多个响应者(在同一条响应链上)同时响应该事件: 通过调用
[super touchesXXX]
。 - 在需要控制全局的用户点击事件的时候,可以通过 UIApplication 的
beginIgnoringInteractionEvents
和endIgnoringInteractionEvnets
来关闭或者开启事件传递。 - 可以重写
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
来扩大UIButton的响应范围(当然缩小也可以)。
-
当目标视图点击区域不在父视图的Frame中时,也能响应事件, 比如咸鱼的tabbar
凸起效果的Tabbar
这里给一个处理的示例代码:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.clipsToBounds && !self.hidden && self.alpha > 0) {
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result != nil) {
return result;
}
}
}
return nil;
}
小结
响应者链是点击事件命中和分配给响应者的过程,它把触摸命中者组成一个链条,让开发人员有机会介入事件的响应过程,从而更灵活的处理点击等事件。
思考题
如何编写hitTest,使得当前视图不响应点击,又不影响子视图响应事件呢?期待你的留言,只有动手写过的代码,才是自己的!