原文:理解点击屏幕的事件响应--->对UIView的hitTest: withEvent: 方法的理解
作者: 岁月峥嵘·我辈当先
当用户点击屏幕后,UIApplication 先响应事件,然后传递给UIWindow。如果window可以响应。就开始遍历window的subviews。遍历的过程中,如果第一个遍历的view1可以响应,那就遍历这个view1的subviews(依次这样不停地查找,直至查找到合适的响应事件view)。如果view1不可以响应,那就开始对view2进行判断和子视图的遍历。依次类推view3,view4…… 如果最后没有找到合适的响应view,这个消息就会被抛弃。(整个遍历的过程就是树的先序遍历)。
理解了上面的图后,我们再来看看这两个方法。
为了方便,我们将
- (nullable UIView*)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;称为方法A
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;称为方法B
对view进行重写这两个方法后,点击屏幕后,首先响应的是方法A;
如果方法A中,我们没有调用父类([super hitTest:point withEvent:event];)的这个方法,那就根据这个方法A的返回view,作为响应事件的view。(当然返回nil,就是这个view不响应)
如果方法A中,我们调用了父类的方法([super hitTest:point withEvent:event];)那这个时候系统就要调用方法B;通过这个方法的返回值,来判断当前这个view能不能响应消息。
如果方法B返回的是no,那就不用再去遍历它的子视图。方法A返回的view就是可以响应事件的view。
如果方法B返回的是YES,那就去遍历它的子视图。(就是上图我们描述的那样,找到合适的view返回,如果找不到,那就由方法A返回的view去响应这个事件。)
总结下来:
返回一个view来响应事件 (如果不想影响系统的事件传递链,在这个方法内,最好调用父类的这个方法)
- (nullable UIView*)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event{
return [super hitTest:point withEvent:event]; }
返回的值可以用来判断是否继续遍历子视图(返回的根据是触摸的point是否在view的frame范围内)
- (BOOL)pointInside:(CGPoint)point withEvent:(nullableUIEvent *)event;
出现视图或者控件无法响应的情况的可能原因
1.self.hidden = YES; (控件被隐藏了)
2.self.userInteractionEnabled = NO (视图的交互性关闭了)
3.self.alpha <= 0.01 (视图的透明度为0.01以下)
4.屏幕点击点 不包含在你需要响应的视图里面(可能是父视图没有frame,或者点击位置超过父视图范围)
· 以上4种问题都可以实现下面这个方法来直接检测出来是否响应
- (UIView*)hitTest:(CGPoint)pointwithEvent:(UIEvent*)event{
if (self.hidden || !self.userInteractionEnabled || self.alpha <= 0.01) {
return nil;//无响应
}
if([self pointInside:point withEvent:event]) {
for(UIView* subView in[self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subView convertPoint:point fromView:self];
UIView* hitTestView = [subView hitTest:convertedPoint withEvent:event];
if(hitTestView) {
return hitTestView;//找到响应的视图 可以响应
}
}
}
return nil;//不响应
//return [super hitTest:point withEvent:event];
}
屏幕穿透点击
两个重叠的视图,响应下面的视图
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView*hitTestView = [subview hitTest:convertedPoint withEvent:event];
//判断视图是否是你想要穿透的视图类 如果是就不响应该视图
if (hitTestView.tag == 2) { return nil; }
if ([hitTestView isKindOfClass:
[UICollectionReusableView class]]) {return nil;// 如果两个重叠的视图不在一个父视图
return 你要响应的视图; //如果两个重叠的视图在同一个父视图上
}
if(hitTestView) {
return hitTestView;
}
}
return[super hitTest:point withEvent:event];
}