用户交互-响应事件的传递 -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

原文:理解点击屏幕的事件响应--->对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];

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。