iOS的事件处理 – hitTest:withEvent:和pointInside:withEvent:如何相关?
虽然大多数苹果文档写得很好,但我认为“ iOS事件处理指南 ”是个例外。 我很难清楚地了解那里描述的内容。
该文件说,
在命中testing中,窗口调用
hitTest:withEvent:
在视图层次结构的最顶层视图上; 此方法通过recursion调用pointInside:withEvent:
在视图层次结构中的每个视图中返回YES,继续向下继续,直到find在其边界内发生触摸的子视图。 这个观点成为了命中testing的观点。
那么是不是只有hitTest:withEvent:
最上面的视图被系统调用,它调用了所有子视图的pointInside:withEvent:
如果从特定子视图返回YES,那么调用pointInside:withEvent:
子视图的子类?
这似乎是一个基本的问题。 但我同意你的文件不如其他文件那么清楚,所以这里是我的答案。
hitTest:withEvent:
在UIResponder中的执行如下:
- 它叫
pointInside:withEvent:
self
- 如果返回是NO,
hitTest:withEvent:
返回nil
。 故事的结尾。 - 如果返回是YES,它将
hitTest:withEvent:
消息发送到它的子视图。 它从顶层子视图开始,继续到其他视图,直到子视图返回一个非零对象,或者所有子视图都接收到该消息。 - 如果子视图第一次返回非零对象,那么第一个
hitTest:withEvent:
将返回该对象。 故事的结尾。 - 如果没有子视图返回非零对象,则第一个
hitTest:withEvent:
返回self
这个过程recursion地重复,所以通常最终返回视图层次的叶子视图。
不过,你可以重写hitTest:withEvent
来做一些不同的事情。 在许多情况下,重写pointInside:withEvent:
更简单,仍然提供了足够的选项来调整应用程序中的事件处理。
我认为你是混淆视图层次结构的子类化。 文档所说的内容如下。 假设你有这个视图层次结构。 按照层次结构,我不是在讨论类层次结构,而是在视图层次结构中的视图,如下所示:
+----------------------------+ |A | |+--------+ +------------+ | ||B | |C | | || | |+----------+| | |+--------+ ||D || | | |+----------+| | | +------------+ | +----------------------------+
说你把你的手指放在D
里面。 以下是会发生的事情:
-
hitTest:withEvent:
在视图层次结构的最顶层视图A
上调用。 -
pointInside:withEvent:
在每个视图上被recursion调用。-
pointInside:withEvent:
在A
上调用,并返回YES
-
pointInside:withEvent:
在B
上调用,并返回NO
-
pointInside:withEvent:
在C
上调用,并返回YES
-
pointInside:withEvent:
在D
上调用,并返回YES
-
- 在返回
YES
的视图上,它将查看层次结构以查看触摸发生的子视图。 在这种情况下,从A
,C
和D
,它将是D
-
D
将是命中testing视图
我发现在iOS中的这个testing非常有帮助
- (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; }
感谢你的回答,他们帮助我用“覆盖”的观点来解决问题。
+----------------------------+ |A +--------+ | | |B +------------------+ | | | |CX | | | | +------------------+ | | | | | | +--------+ | | | +----------------------------+
假设X
– 用户的触摸。 pointInside:withEvent:
在B
返回NO
,所以hitTest:withEvent:
返回A
我写了UIView
类别来处理问题,当你需要接触顶部最显眼的视图。
- (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event { // 1 if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0) return nil; // 2 UIView *hitView = self; if (![self pointInside:point withEvent:event]) { if (self.clipsToBounds) return nil; else hitView = nil; } // 3 for (UIView *subview in [self.subviewsreverseObjectEnumerator]) { CGPoint insideSubview = [self convertPoint:point toView:subview]; UIView *sview = [subview overlapHitTest:insideSubview withEvent:event]; if (sview) return sview; } // 4 return hitView; }
- 我们不应该为隐藏或透明视图发送触摸事件,或者将
userInteractionEnabled
设置为NO
视图发送; - 如果触摸在
self
内部,self
将被视为潜在的结果。 - recursion地检查所有的子视图。 如果有的话,退还。
- 否则根据步骤2的结果返回自己或零。
请注意, [self.subviewsreverseObjectEnumerator]
需要遵循从顶部到底部的视图层次结构。 并检查clipsToBounds
以确保不testing蒙面子视图。
用法:
- 在子类视图中导入类别。
- 用
hitTest:withEvent:
replacehitTest:withEvent:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return [self overlapHitTest:point withEvent:event]; }
官方苹果指南也提供了一些很好的插图。
希望这有助于某人。
它显示这样的片段!
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01) { return nil; } if (![self pointInside:point withEvent:event]) { return nil; } __block UIView *hitView = self; [self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { CGPoint thePoint = [self convertPoint:point toView:obj]; UIView *theSubHitView = [obj hitTest:thePoint withEvent:event]; if (theSubHitView != nil) { hitView = theSubHitView; *stop = YES; } }]; return hitView; }
@lion的片段就像一个魅力。 我将其移植到swift 2.1并将其用作UIView的扩展。 我张贴在这里,以防有人需要它。
extension UIView { func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { // 1 if !self.userInteractionEnabled || self.hidden || self.alpha == 0 { return nil } //2 var hitView: UIView? = self if !self.pointInside(point, withEvent: event) { if self.clipsToBounds { return nil } else { hitView = nil } } //3 for subview in self.subviews.reverse() { let insideSubview = self.convertPoint(point, toView: subview) if let sview = subview.overlapHitTest(insideSubview, withEvent: event) { return sview } } return hitView } }
要使用它,只需在您的uiview中覆盖hitTest:point:withEvent,如下所示:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { let uiview = super.hitTest(point, withEvent: event) print("hittest",uiview) return overlapHitTest(point, withEvent: event) }