iOS 的事件处理 - hitTest:withEvent: 和 pointInside:withEvent: 是如何相关的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4961386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Event handling for iOS - how hitTest:withEvent: and pointInside:withEvent: are related?
提问by realstuff02
While most apple documents are very well written, I think 'Event Handling Guide for iOS' is an exception. It's hard for me to clearly understand what's been described there.
虽然大多数苹果文档都写得很好,但我认为“ iOS 事件处理指南”是一个例外。我很难清楚地理解那里描述的内容。
The document says,
文件说,
In hit-testing, a window calls
hitTest:withEvent:
on the top-most view of the view hierarchy; this method proceeds by recursively callingpointInside:withEvent:
on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.
在命中测试中,一个窗口调用
hitTest:withEvent:
视图层次结构的最顶层视图;此方法通过递归调用pointInside:withEvent:
视图层次结构中返回 YES 的每个视图来继续,沿层次结构向下进行,直到找到发生触摸的边界内的子视图。该视图成为命中测试视图。
So is it like that only hitTest:withEvent:
of the top-most view is called by the system, which calls pointInside:withEvent:
of all of subviews, and if the return from a specific subview is YES, then calls pointInside:withEvent:
of that subview's subclasses?
那么是不是系统只hitTest:withEvent:
调用最顶层的视图,调用pointInside:withEvent:
所有的子视图,如果某个子视图的返回值为YES,则调用pointInside:withEvent:
该子视图的子类?
回答by pgb
I think you are confusing subclassing with the view hierarchy. What the doc says is as follows. Say you have this view hierarchy. By hierarchy I'm not talking about class hierarchy, but views within views hierarchy, as follows:
我认为您将子类与视图层次结构混淆了。医生说的内容如下。假设您有这个视图层次结构。按层次结构我不是在谈论类层次结构,而是在视图层次结构中的视图,如下所示:
+----------------------------+
|A |
|+--------+ +------------+ |
||B | |C | |
|| | |+----------+| |
|+--------+ ||D || |
| |+----------+| |
| +------------+ |
+----------------------------+
Say you put your finger inside D
. Here's what will happen:
假设你把你的手指放在里面D
。下面是会发生的事情:
hitTest:withEvent:
is called onA
, the top-most view of the view hierarchy.pointInside:withEvent:
is called recursively on each view.pointInside:withEvent:
is called onA
, and returnsYES
pointInside:withEvent:
is called onB
, and returnsNO
pointInside:withEvent:
is called onC
, and returnsYES
pointInside:withEvent:
is called onD
, and returnsYES
- On the views that returned
YES
, it will look down on the hierarchy to see the subview where the touch took place. In this case, fromA
,C
andD
, it will beD
. D
will be the hit-test view
hitTest:withEvent:
A
在视图层次结构的最顶层视图上调用。pointInside:withEvent:
在每个视图上递归调用。pointInside:withEvent:
被调用A
,并返回YES
pointInside:withEvent:
被调用B
,并返回NO
pointInside:withEvent:
被调用C
,并返回YES
pointInside:withEvent:
被调用D
,并返回YES
- 在返回的视图上
YES
,它将向下查看层次结构以查看发生触摸的子视图。在这种情况下,从A
,C
和D
,它将是D
。 D
将是命中测试视图
回答by MHC
It seems quite a basic question. But I agree with you the document is not as clear as other documents, so here is my answer.
这似乎是一个非常基本的问题。但我同意你的意见,该文件不像其他文件那样清晰,所以这是我的回答。
The implementation of hitTest:withEvent:
in UIResponder does the following:
的实施hitTest:withEvent:
在UIResponder执行以下操作:
- It calls
pointInside:withEvent:
ofself
- If the return is NO,
hitTest:withEvent:
returnsnil
. the end of the story. - If the return is YES, it sends
hitTest:withEvent:
messages to its subviews. it starts from the top-level subview, and continues to other views until a subview returns a non-nil
object, or all subviews receive the message. - If a subview returns a non-
nil
object in the first time, the firsthitTest:withEvent:
returns that object. the end of the story. - If no subview returns a non-
nil
object, the firsthitTest:withEvent:
returnsself
- 它调用
pointInside:withEvent:
的self
- 如果返回为 NO,则
hitTest:withEvent:
返回nil
。故事的结局。 - 如果返回是 YES,它会向
hitTest:withEvent:
它的子视图发送消息。它从顶层子视图开始,一直到其他视图,直到一个子视图返回一个非nil
对象,或者所有子视图都收到消息。 - 如果子视图
nil
第一次返回非对象,则第一个hitTest:withEvent:
返回该对象。故事的结局。 - 如果没有子视图返回非
nil
对象,则第一个hitTest:withEvent:
返回self
This process repeats recursively, so normally the leaf view of the view hierarchy is returned eventually.
这个过程递归地重复,所以通常最终返回视图层次结构的叶视图。
However, you might override hitTest:withEvent
to do something differently. In many cases, overriding pointInside:withEvent:
is simpler and still provides enough options to tweak event handling in your application.
但是,您可能会覆盖hitTest:withEvent
以执行不同的操作。在许多情况下,覆盖pointInside:withEvent:
更简单,并且仍然提供足够的选项来调整应用程序中的事件处理。
回答by onmyway133
I find this Hit-Testing in iOSto be very helpful
我发现iOS 中的命中测试非常有用
- (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;
}
Edit Swift 4:
编辑斯威夫特 4:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
return super.hitTest(point, with: event)
}
guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
return nil
}
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return nil
}
回答by Lion
Thanks for answers, they helped me to solve situation with "overlay" views.
感谢您的解答,他们帮我解决的情况与“叠加”的观点。
+----------------------------+
|A +--------+ |
| |B +------------------+ |
| | |C X | |
| | +------------------+ |
| | | |
| +--------+ |
| |
+----------------------------+
Assume X
- user's touch. pointInside:withEvent:
on B
returns NO
, so hitTest:withEvent:
returns A
. I wrote category on UIView
to handle issue when you need to receive touch on top most visibleview.
假设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;
}
- We should not send touch events for hidden or transparent views, or views with
userInteractionEnabled
set toNO
; - If touch is inside
self
,self
will be considered as potential result. - Check recursively all subviews for hit. If any, return it.
- Else return self or nil depending on result from step 2.
- 我们不应该为隐藏或透明视图或
userInteractionEnabled
设置为 的视图发送触摸事件NO
; - 如果触摸在里面
self
,self
将被视为潜在的结果。 - 递归检查所有子视图是否命中。有的话,退货。
- 否则根据第 2 步的结果返回 self 或 nil。
Note, [self.subviewsreverseObjectEnumerator]
needed to follow view hierarchy from top most to bottom. And check for clipsToBounds
to ensure not to test masked subviews.
注意,[self.subviewsreverseObjectEnumerator]
需要从顶部到底部遵循视图层次结构。并检查clipsToBounds
以确保不测试被屏蔽的子视图。
Usage:
用法:
- Import category in your subclassed view.
- Replace
hitTest:withEvent:
with this
- 在子类视图中导入类别。
hitTest:withEvent:
用这个替换
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self overlapHitTest:point withEvent:event];
}
Official Apple's Guideprovides some good illustrations too.
Apple 官方指南也提供了一些很好的插图。
Hope this helps somebody.
希望这可以帮助某人。
回答by hippo
It shows like this snippet!
它显示像这个片段!
- (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;
}
回答by mortadelo
The snippet of @lion works like a charm. I ported it to swift 2.1 and used it as an extension to UIView. I'm posting it here in case somebody needs it.
@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
}
}
To use it, just override hitTest:point:withEvent in your uiview as follows:
要使用它,只需在您的 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)
}
回答by yoAlex5
Class diagram
类图
Hit Testing
命中测试
Find a First Responder
找到 First Responder
First Responder
in this case is the deepest UIView
point()
method of which returned true
First Responder
在这种情况下是UIView
point()
返回 true的最深方法
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
func point(inside point: CGPoint, with event: UIEvent?) -> Bool
Internally hitTest()
looks like
内部hitTest()
看起来像
hitTest() {
if (isUserInteractionEnabled == false || isHidden == true || alpha == 0 || point() == false) { return nil }
for subview in subviews {
if subview.hitTest() != nil {
return subview
}
}
return nil
}
Send Touch Event to the First Responder
将触摸事件发送到 First Responder
//UIApplication.shared.sendEvent()
//UIApplication, UIWindow
func sendEvent(_ event: UIEvent)
//UIResponder
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
Let's take a look at example
我们来看一个例子
Responder Chain
响应链
//UIApplication.shared.sendAction()
func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool
Take a look at example
看个例子
class AppDelegate: UIResponder, UIApplicationDelegate {
@objc
func foo() {
//this method is called using Responder Chain
print("foo") //foo
}
}
class ViewController: UIViewController {
func send() {
UIApplication.shared.sendAction(#selector(AppDelegate.foo), to: nil, from: view1, for: nil)
}
}