美文网首页iOS为了更好的活着iOS开发技术
iOS事件响应链中Hit-Test View的应用

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

作者: 孙大龙Lemon | 来源:发表于2015-12-12 11:19 被阅读22138次

最近又看了遍苹果的官方文档《Event Handling Guide for iOS》,对事件响应链中的hit-test view 又多了些理解,个人觉的官方文档对这块讲的非常简单,很多东西都是点到为止,hit-test view的知识在项目的任何地方都用到了,但自己反而感知不到,接下来我会给大家讲hit-test view的原理,以及项目中能解决痛点的三个应用 。

什么叫 hit-test view?文档说:The lowest view in the view hierarchy that contains the touch point becomes the hit-test view,我的理解是:当你点击了屏幕上的某个view,这个动作由硬件层传导到操作系统,然后又从底层封装成一个事件(Event)顺着view的层级往上传导,一直要找到含有这个点击点层级最高(文档说是最低,我理解是视图树的根节点,或者最靠近你的手指的view)的view来响应事件,这个view就是hit-test view

文档中说,决定谁hit-test view是通过不断递归调用view中的 - (UIView *)hitTest: withEvent: 方法和 -(BOOL)pointInside: withEvent: 方法来实现的,文段中的这段话太好理解,于是我仿照官方文档中这张图做了个Demo -> Github地址

apple doucument pic

重载图中view的方法添加相应的log便于观察:

//in every view .m overide those methods
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"进入A_View---hitTest withEvent ---");
    UIView * view = [super hitTest:point withEvent:event];
    NSLog(@"离开A_View--- hitTest withEvent ---hitTestView:%@",view);
    return view;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
    NSLog(@"A_view--- pointInside withEvent ---");
    BOOL isInside = [super pointInside:point withEvent:event];
    NSLog(@"A_view--- pointInside withEvent --- isInside:%d",isInside);
    return isInside;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"A_touchesBegan");
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    NSLog(@"A_touchesMoved");
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    NSLog(@"A_touchesEnded");
}

点击图中View_D,看下会发生什么

View_D log

一是发现touchesBegan、touchesMoved、touchesEnded这些方法都是发生在找到hit-test view之后,因为touch事件是针对能响应事件的确定的某个view,比如你手指划出了scrollview的范围,只要你不松手继续滑动,scrollview依然会响应滑动事件继续滚动;二是寻找hit-test view的事件链传导了两遍,而且两次的调用堆栈是不同的,这点我有点搞不懂,为啥需要两遍,查阅了很多资料也不知道原因,发现真机和模拟器以及不同的系统版本之间还会有些区别(此为真机iOS9),大家可以下载我的Demo进行测试与研究。

把这个寻找的逻辑换成代码如下:

- (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 Programming Guide for iOS文档中说的 Visually, the content of a subview obscures all or part of the content of its parent view. If the subview is totally opaque, then the area occupied by the subview completely obscures the corresponding area of the parent. If the subview is partially transparent, the content from the two views is blended together prior to being displayed on the screen. Each superview stores its subviews in an ordered array and the order in that array also affects the visibility of each subview. If two sibling subviews overlap each other, the one that was added last (or was moved to the end of the subview array) appears on top of the other. 那最高层(逻辑最靠近手指的)view是view subviews数组的最后一个元素,只要寻找是从数组的第一个元素开始遍历,hit-test view的逻辑依然是有效的。

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

Hit-test view的应用举例:

1、扩大UIButton的响应热区

相信大家都遇到小图button点击热区太小问题,之前我是用UIButton的setImage方法来设置图片解决,但是调起坐标就坑了,得各种计算不说,写出的代码还很难看不便于维护,如果我们用用hit-test view的知识你就能轻松地解决这个问题。

重载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中间的大按钮,如下图闲鱼的app,点击超出Tabbar bounds的区域也需要响应,此时重载父view的-(UIView *)hitTest: withEvent:方法,去掉点击必须在父view内的判断,然后子view就能成为 hit-test view用于响应事件了。

xiansyu
- (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滑动

这是app store 应用的app封面预览功能

scrollview page

上图的交互常常见于很多海报、封面展示的app,实现这个交互的方法有很多,但选择用scrollView来横向滑动来做是最简单的,让scrollview.pageEnabel = YES,就有了翻页的感觉,但这样scoreView的实际可滑动区域就只有一张照片那么宽,如果想让边侧留出的距离(蓝色框部分)响应滑动事件的话应该怎么办呢?这个时候又可以用到hit-test view的知识了,在scrollview的父view中把蓝色部分的事件都传递给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;
}

总结

事件响应链是UI层一个非常重要的概念,想做出非常棒的交互和动画,必须对其有一个深入的理解。我列举的只是我在开发中遇到的一些问题,如果有其他的对事件响应链的应用希望大家和我一起交流探讨。

相关文章

  • 响应链

    iOS事件响应链中Hit-Test View的应用从iOS的事件响应链看TableView为什么不响应touche...

  • Hit-Test 原理及应用案例

    Hit-Test和响应链 什么叫 hit-test view?文档说:The lowest view in the...

  • iOS事件响应链中Hit-Test View的应用

    最近又看了遍苹果的官方文档《Event Handling Guide for iOS》,对事件响应链中的hit-t...

  • 转载:响应者链工作原理

    响应者链 响应者链是由一个一个响应者组成的长链;响应者链定义了iOS中触摸事件的交互规则;如果hit-test检测...

  • iOS事件响应链Hit-Test View

    Hit-Test View的大致理解 官方文档: The lowest view in the view hier...

  • 事件的响应链与传递链

    iOS事件链有两条:事件的响应链;Hit-Testing事件的传递链 响应链:由离用户最近的view向系统传递。i...

  • iOS 中事件的响应链和传递链

    iOS事件链有两条:事件的响应链;Hit-Testing事件的传递链 响应链:由离用户最近的view向系统传递。i...

  • iOS 中事件的响应链和传递链

    iOS事件链有两条:事件的响应链;Hit-Testing事件的传递链 响应链:由离用户最近的view向系统传递。i...

  • iOS 响应链

    iOS开发 - 事件传递响应链iOS 响应者链,事件的传递事件传递之响应链Cocoa Touch事件处理流程--响...

  • dailyLearning -- 响应者链

    响应者对象介绍 什么是响应者链 事件响应流程(事件的产生和传递) 怎么寻找最合适的 view 应用 在runLoo...

网友评论

  • PGOne爱吃饺子:大佬 可以问你关于这一节的一个问题么
  • 笨鸟后飞了:写的很不错,给力
  • 英俊神武:你好 minHitTestWidth 这个值它的值从哪里来的,还是说默认为0
  • singlestep:《Event Handling Guide for iOS》 文档点击失效了 , 麻烦更新下吧
  • 过客Zhaopy:请问博主第3个例子中的效果,用scrollView如何实现?
  • 半江瑟瑟:大神 我现在有一个需求,很多vc 里面 如果有tableview 等滚动视图的话,是可以滚动的,但是不能触发任何事件,比如不能点击button ,不能点击cell 不能点击cell里面的东西,不能点击tableview里面的控件,总之除了能够滑动滚动视图,别的都不行,当然左上角的返回按钮 返回上一页是可以的,如何实现
    zyzxrj:禁用子类的响应就可以了呀
  • Ko_Neko:请问一下博主,HitTestTool里的声明中直接写了一个CGRect

    CGRect HitTestingBounds(CGRect bounds, CGFloat minimumHitTestWidth, CGFloat minimumHitTestHeight);

    在实现中还重载了,这种写法没有见过。请问属性中这个方法是一个Getter吗?如果是那么.h文件前面怎么可以不加 @property呢?
    TengFengLian:c 函数方法的形式
  • 0o冻僵的企鹅o0:请问你有写demo吗?那个page页滑动好像无效
  • keshiim:写的不错,mark了
  • 吃蘑菇De大灰狼:文档说的 farthest descendant是指的是叶子节点吧~就是手指所点击的View
    我的理解:点击一个view,系统先循环调用hit-Text 遍历视图栈里的节点,找到叶子节点(所点击的View),然后在封装一个包含UITouch对象的UIEvent事件,用Responder Chain响应链传递一层层给根节点AppDelegate处理~
    孙大龙Lemon:视图树根节点,数据结构学好了,一语中的
  • cc_Jumper:mark。。。
  • qazws:偷大龙
  • qBryant:学习。。。
  • 83d82e6f3b15:2、子view超出了父view的bounds响应事件
    的代码中,
    return self;
    要注释也一起注释吧, 不然坨坨返回非空对象了..
  • wingsrao:随着iOS学习程度的加深,这些知识是必不可少的。

本文标题:iOS事件响应链中Hit-Test View的应用

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