昨天被问到了一个问题,如下
一个子视图能不能透过上层视图来响应自己的点击事件?如果能,怎么实现?
这种题用脚想都是能啊,可怎么做???
经过简单研究,发现了解决办法。如下:
这里涉及到一个视图的方法
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
这是在UIView(UIViewGeometry)
视图结构分类里面的方法,使用方法如下:
1. 背景知识
iOS系统检测到手指触摸(Touch)操作时会将其放入当前活动Application的事件队列,Application会从事件队列中取出触摸事件并传递给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。
2. 处理流程
- 首先调用当前视图的
pointInside:withEvent:
方法判断触摸点是否在当前视图内; - 若返回NO,则
hitTest:withEvent:
返回nil; - 若返回YES,则向当前视图的所有子视图(subviews)发送
hitTest:withEvent:
消息,所有子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕; - 若第一次有子视图返回非空对象,则
hitTest:withEvent:
方法返回此对象,处理结束; - 如所有子视图都返回非,则
hitTest:withEvent:
方法返回自身(self)。
3. 使用
直接参考代码
// 在父视图中实现方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.这是当前点击的视图,如果没有找到合适的响应操作的视图,则直接返回这个
UIView *view = [super hitTest:point withEvent:event];
// 2.将父视图坐标转成我想要响应事件的视图的坐标
CGPoint buttonPoint = [self convertPoint:point toView:self.button];
// 3.判断该坐标是否在视图内部,如果是,则返回该视图
if ([self.button pointInside:buttonPoint withEvent:event]) {
return self.button;
}
return view;
}
代码写起来也是简单明了,但有几个注意事项,如下:
- 设置
hidden = YES;
仍然是可以通过该方法响应事件。 - 设置
alpha = 0.001;
也是可以通过该方法响应事件。 - 子视图的 origin 即使超出父视图的范围也是可以响应事件。
- 设置
userInteractionEnabled = NO;
则不可以响应事件了。
这种方式可以做出很多风骚的操作,就看各位的想象力了~