美文网首页
Hit-Test 原理及应用案例

Hit-Test 原理及应用案例

作者: sheepcao | 来源:发表于2018-02-18 16:33 被阅读19次

    Hit-Test和响应链

    什么叫 hit-test view?文档说:The lowest view in the view hierarchy that contains the touch point becomes the hit-test view,我的理解是:当你点击了屏幕上的某个view,这个动作由硬件层传导到操作系统,然后又从底层封装成一个事件(Event),从keyWindow开始顺着view的层级往上传导,一直要找到含有这个点击点且层级最高的view来响应事件,这个view就是hit-test view。
    如果在hit-test中调用默认的super hittest:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView * view = [super hitTest:point withEvent:event];
        return view;
    }
    

    则其内在的行为等价于:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        if (!self.isUserInteractionEnabled || self.isHidden || 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 self;
        }
        return nil;
    }
    

    如果有某个view的两个子view位置重叠,那最高层(逻辑最靠近手指的)view是view subviews数组的最后一个元素,只要寻找是从数组的第一个元素开始遍历,hit-test view的逻辑依然是有效的。(即reverseObjectEnumerator逆序枚举中的首个元素执行到return self的优先级最高。)


    hit-test

    找到hit-test view后,它会有最高的优先权去响应逐级传递上来的Event,如它不能响应就会传递给它的superview,依此类推,一直传递到UIApplication都无响应者,这个Event就会被系统丢弃了。


    responder chain

    可以看到,持有View的View Controller会先于super View得到响应时间的机会。

    应用举例

    1、扩大UIButton的响应热区

    重载UIButton的-(BOOL)pointInside: withEvent:方法,让Point即使落在Button的Frame外围也返回YES。

    //in custom button .m
    //overide this method
    - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
        return CGRectContainsPoint(HitTestingBounds(self.bounds, self.minimumHitTestWidth, self.minimumHitTestHeight), point);
    }
    
    CGRect HitTestingBounds(CGRect bounds, CGFloat minimumHitTestWidth, CGFloat minimumHitTestHeight) {
        CGRect hitTestingBounds = bounds;
        if (minimumHitTestWidth > bounds.size.width) {
            hitTestingBounds.size.width = minimumHitTestWidth;
            hitTestingBounds.origin.x -= (hitTestingBounds.size.width - bounds.size.width)/2;
        }
        if (minimumHitTestHeight > bounds.size.height) {
            hitTestingBounds.size.height = minimumHitTestHeight;
            hitTestingBounds.origin.y -= (hitTestingBounds.size.height - bounds.size.height)/2;
        }
        return hitTestingBounds;
    }
    

    2、子view超出了父view的bounds响应事件
    项目中常常遇到button已经超出了父view的范围但仍需可点击的情况,比如自定义Tabbar中间的大按钮,如在底部TabberBar中间放置宇哥大按钮,点击超出Tabbar bounds的区域也需要响应,此时重载父view的-(UIView *)hitTest: withEvent:方法,去掉点击必须在父view内的判断,然后子view就能成为 hit-test view用于响应事件了。

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        
        if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
            return nil;
        }
        /**
         *  此注释掉的方法用来判断点击是否在父View Bounds内,
         *  如果不在父view内,就会直接不会去其子View中寻找HitTestView,return 返回
         */
    //    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 self;
    //    }
     //   return nil;
    }
    

    3、ScrollView page滑动
    当使用scrollview进行分页显示的时候(PageEnable = YES),分页的宽度是scrollview的宽度,通常我们使用scrollview全屏显示内容,如果我们需要半屏幕宽或者其他小于屏幕宽度的分页,则需要以下步骤:

    • 设置你的UIScrollView的宽度为Width/2;
    • 开启分页模式:self.pagingEnabled = YES;
    • 关闭self.clipsToBounds = NO; 这样超出范围的视图也会显示。
    • 然后重写UIScrollView所在的parentView的hitTest事件,让其返回值是UIScrollView对象. 此举可以使scrollview两侧的区域也能响应scrollview的滑动事件。
      第四部的代码如下:
    //in scrollView.superView .m
    
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        
        UIView *hitTestView = [super hitTest:point withEvent:event];
        if (hitTestView) {
            hitTestView = self.scrollView;
        }
        return hitTestView;
    }
    

    参考:iOS事件响应链中Hit-Test View的应用

    相关文章

      网友评论

          本文标题:Hit-Test 原理及应用案例

          本文链接:https://www.haomeiwen.com/subject/rmrbtftx.html