ほとんどのAppleドキュメントは非常によく書かれていますが、「 iOSのイベント処理ガイド 」は例外だと思います。そこに記載されていることを明確に理解するのは難しいです。
文書によれば、
ヒットテストでは、ウィンドウはビュー階層の一番上のビューで
hitTest:withEvent:
を呼び出します。このメソッドは、ビュー階層内の各ビューでpointInside:withEvent:
を再帰的に呼び出してYESを返し、タッチが行われた境界内でサブビューが見つかるまで階層を下っていきます。そのビューがヒットテストビューになります。
最上位ビューのhitTest:withEvent:
のみがシステムによって呼び出され、すべてのサブビューのpointInside:withEvent:
が呼び出され、特定のサブビューからの戻りがYESの場合はpointInside:withEvent:
そのサブビューのサブクラスの?
かなり基本的な質問のようです。しかし、私はあなたの文書が他の文書ほど明確ではないことに同意するので、ここに私の答えがあります。
UIResponderのhitTest:withEvent:
の実装は次のことを行います。
self
のpointInside:withEvent:
を呼び出しますhitTest:withEvent:
はnil
を返します。物語の終わり。hitTest:withEvent:
メッセージをそのサブビューに送信します。最上位のサブビューから開始し、サブビューがnil
以外のオブジェクトを返すか、すべてのサブビューがメッセージを受信するまで、他のビューに続きます。nil
以外のオブジェクトを返す場合、最初のhitTest:withEvent:
はそのオブジェクトを返します。物語の終わり。nil
以外のオブジェクトを返す場合、最初のhitTest:withEvent:
はself
を返しますこのプロセスは再帰的に繰り返されるため、通常は最終的にビュー階層のリーフビューが返されます。
ただし、hitTest:withEvent
をオーバーライドして、何か別のことを行うことができます。多くの場合、pointInside:withEvent:
のオーバーライドはより簡単であり、アプリケーションでイベント処理を微調整するための十分なオプションを提供します。
サブクラス化とビュー階層を混同していると思います。ドキュメントには次のように書かれています。このビュー階層があるとします。階層では、次のようにクラス階層についてではなく、ビュー階層内のビューについて説明しています。
+----------------------------+
|A |
|+--------+ +------------+ |
||B | |C | |
|| | |+----------+| |
|+--------+ ||D || |
| |+----------+| |
| +------------+ |
+----------------------------+
D
の中に指を置いたとします。起こることは次のとおりです。
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
はヒットテストビューになります私はこれを見つける 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;
}
Swift 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
}
答えてくれてありがとう、彼らは「オーバーレイ」ビューで状況を解決するのを助けてくれました。
+----------------------------+
|A +--------+ |
| |B +------------------+ |
| | |C X | |
| | +------------------+ |
| | | |
| +--------+ |
| |
+----------------------------+
X
-ユーザーのタッチを想定します。 pointInside:withEvent:
on B
はNO
を返すため、hitTest:withEvent:
はA
を返します。一番上visibleビューでタッチを受け取る必要がある場合に問題を処理するために、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
は潜在的な結果と見なされます。注:[self.subviewsreverseObjectEnumerator]
は、ビューの階層を上から下に辿る必要がありました。そして、clipsToBounds
をチェックして、マスクされたサブビューをテストしないようにします。
使用法:
hitTest:withEvent:
をこれに置き換えます- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self overlapHitTest:point withEvent:event];
}
Official Apple's Guide もいくつかの良いイラストを提供しています。
これが誰かを助けることを願っています。
このスニペットのように表示されます!
- (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)
}