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里面。 以下是会发生的事情:

  1. hitTest:withEvent:在视图层次结构的最顶层视图A上调用。
  2. pointInside:withEvent:在每个视图上被recursion调用。
    1. pointInside:withEvent:A上调用,并返回YES
    2. pointInside:withEvent:B上调用,并返回NO
    3. pointInside:withEvent:C上调用,并返回YES
    4. pointInside:withEvent:D上调用,并返回YES
  3. 在返回YES的视图上,它将查看层次结构以查看触摸发生的子视图。 在这种情况下,从ACD ,它将是D
  4. 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; } 
  1. 我们不应该为隐藏或透明视图发送触摸事件,或者将userInteractionEnabled设置为NO视图发送;
  2. 如果触摸在self内部, self将被视为潜在的结果。
  3. recursion地检查所有的子视图。 如果有的话,退还。
  4. 否则根据步骤2的结果返回自己或零。

请注意, [self.subviewsreverseObjectEnumerator]需要遵循从顶部到底部的视图层次结构。 并检查clipsToBounds以确保不testing蒙面子视图。

用法:

  1. 在子类视图中导入类别。
  2. 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) }