关于hit-testing机制

来源:互联网 发布:七天网络查分网页登陆 编辑:程序博客网 时间:2024/05/16 17:43

当我们点击屏幕上某个点的时候, IOS会检查到手指触摸操作(Touch),并生产一个UITouch对象,将其打包成一个UIEvent对象。然后将其放入当前活动的Application的 事件对列 , UIApplication会从 事件对列 中按照对列的顺序,通常先分发给应用程序主窗口,主窗口会调用hitTest:withEvent:方法(假设称为方法A,注意这是UIView的方法),查找合适的事件触发视图(这里通常称为“hit-test view”)。
看下面的图就一目了然
hit-testing

关于方法- hitTest withEvent:
主要原理是:
若某个类重写了 hitTest 方法,该方法会先判断
1.是否满足以下三种情况,
self.userInteractionEnabled = NO
self.hidden = YES
self.alpha <= 0.01
这三种情况时当前视图就不再接收消息了,直接返回 nil。
2.判断触摸点是否在该类范围内,若pointInside返回 YES就倒序遍历 subview,subView 调用自己的 hit 方法,一层subview一层subview 的找下去,若最终找到一个目标 view,该类的 hit 方法就返回这个 目标view,若没有这个 view,则返回 self。若pointInside返回 NO 返回 nil。

上代码:

- (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;}

首先MainView 上的子view包括 viewA,ViewB(ViewB1,ViewB2),ViewC,添加顺序也是依次添加。

由图和代码我们可以看出:遍历当前视图的所有子视图的顺序是从当前视图的subviews数组的末尾向前遍历(倒序),即先判断最后添加到当前视图上的子视图

下面是整个触摸的传递过程,传递到不同的 View 调用- hitTest withEvent:判断触摸点是否在当前视图范围内
1.主 window:-pointInside: withEvent:–>return YES,以此为根节点向下进行倒序遍历搜索
2.MainView :-pointInside: withEvent:–>return YES,以此为根节点向下进行倒序遍历搜索
3.ViewC:-pointInside: withEvent:–>return NO,不再向下进行搜索
4.ViewB :-pointInside: withEvent:–> YES,以此为根节点向下进行搜索
5.ViewB2 :–> NO,不再向下进行搜索
6.ViewB1 :–> YES,好了到这 ViewB2没有子视图(或者子视图的 hitView 都返回了nil),直接返回 self

至此,我们已经找到了 hitView,接下来把 hitView 向上传递给 UIApplication,UIApplication中有个sendEvent:的方法,在UIWindow中同样也可以发现一个同样的方法。UIApplication是通过这个方法把事件发送给UIWindow,然后UIWindow通过同样的接口,把事件发送给hit-testview,此时hitView会调用touchesBegan方法响应事件
下面来看几种情况:
1.当前view,包括子 view 都不接收事件,将事件传递给兄弟viewA:当前view 重写- hitTest withEvent:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {    return nil;}

2.当前view不接收事件,但是,子 view 要接收,将事件传递给兄弟viewA(即:点击当前 view 空白的地方,让和他重叠的兄弟 view 接收事件):当前view 重写- hitTest withEvent:

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

3.设置 scrollView 的分页宽度。现在有这么一个需求:有一个 VC,上面有一个parentView,parentView 上面有个 scrollView,scrollView 上显示三张图片,滑动一下,就会滚出来一张图片,看清楚一张,就是说 scrollView 的分页是1/3VC.View。
思路:
设置scrollView的宽度为1/3VC.View。//这样分页宽度就是自己的宽度。
设置scrollView的 center 横向居中。
设置 scrollView.contentoffset = 1/3VC.View。//这样就显示了3张图片
开启分页模式:scrollView.pagingEnabled = YES。
设置scrollView.clipsToBounds = NO。//这样超出范围的视图也会显示。
然后重写scrollView所在的parentView的hitTest事件,让其返回值是UIScrollView对象。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {  return [self pointInside:point withEvent:event] ? scrollView : nil;}
0 0