UIViewの境界を超えた相互作用


92

UIButtonのフレームがその親のフレームの外側にある場合、UIButton(またはそのほかのコントロール)がタッチイベントを受け取ることは可能ですか?これを試すと、UIButtonがイベントを受信できないようです。これを回避するにはどうすればよいですか?


1
これは完璧に機能します。developer.apple.com/library/ios/qa/qa2013/qa1812.html Best
Frank Li

2
また、これは良いと関連する(あるいはだまされやすい人)である:stackoverflow.com/a/31420820/8047
ダンRosenstark

回答:


93

はい。hitTest:withEvent:メソッドをオーバーライドして、ビューに含まれているよりも大きなポイントセットのビューを返すことができます。UIViewクラスリファレンスを参照してください。

編集:例:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(-radius, -radius,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}

編集2:(説明後:)ボタンが親の境界内にあるものとして扱われるようにpointInside:withEvent:するには、ボタンのフレームを含めるように親でオーバーライドする必要があります。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (CGRectContainsPoint(self.view.bounds, point) ||
        CGRectContainsPoint(button.view.frame, point))
    {
        return YES;
    }
    return NO;
}

pointInsideをオーバーライドするためにそこにあるコードはまったく正しくないことに注意してください。Summonが以下で説明するように、これを行います:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
    if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
        return YES;

    return [super pointInside:point withEvent:event];
    }

self.oversizeButtonこのUIViewサブクラスのIBOutletとして使用する可能性が非常に高いことに注意してください。次に、問題の「特大ボタン」を問題の特別なビューにドラッグするだけです。(または、何らかの理由でプロジェクトでこれを頻繁に行っている場合は、特別なUIButtonサブクラスがあり、それらのクラスのサブビューリストを調べることができます。)お役に立てば幸いです。


これをいくつかのサンプルコードで正しく実装するために私をさらに助けてもらえますか?また、リファレンスを読んだところ、次の行が原因で混乱しています。「レシーバーの境界の外にあるポイントは、実際にはレシーバーのサブビューの1つにある場合でも、ヒットとして報告されることはありません。親ビューのclipsToBoundsプロパティがNOに設定されている場合は、親の親の。ただし、ヒットテストでは、親ビューの境界外のポイントは常に無視されます。」
ThomasM 2011年

特に、これが私が必要とする解決策ではないように見えるのはこの部分です:「ただし、ヒットテストでは常に親ビューの境界の外にあるポイントは無視されます。」しかし、もちろん間違っている可能性があります。 ..
ThomasM

ああ、わかりました。pointInside:withEvent:ボタンのフレームを含めるには、親をオーバーライドする必要があります。上記の回答にサンプルコードを追加します。
jnic

UIViewのメソッドをオーバーライドせずにこれを行う方法はありますか?たとえば、ビューがUIKitに対して完全にジェネリックであり、Nibを介して初期化されている場合、UIViewController内からこれらのメソッドをオーバーライドできますか?
RLH 2012

1
hitTest:withEvent:pointInside:withEvent:親の境界外にあるボタンを押しても、エリアは呼び出されません。何が悪いのでしょうか?
iosdude 2017年

24

@jnic、私はiOS SDK 5.0に取り組んでおり、あなたのコードを正しく機能させるために私はこれをしなければなりませんでした:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(button.frame, point)) {
    return YES;
}
return [super pointInside:point withEvent:event]; }

私の場合のコンテナービューはUIButtonであり、すべての子要素も親UIButtonの境界の外に移動できるUIButtonです。

ベスト


20

親ビューでは、ヒットテストメソッドをオーバーライドできます。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGPoint translatedPoint = [_myButton convertPoint:point fromView:self];

    if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) {
        return [_myButton hitTest:translatedPoint withEvent:event];
    }
    return [super hitTest:point withEvent:event];

}

この場合、ポイントがボタンの範囲内にある場合は、そこに通話を転送します。そうでない場合は、元の実装に戻します。


17

私の場合、を含むUICollectionViewCellサブクラスがありましたUIButtonclipsToBoundsセルで無効にすると、ボタンがセルの境界の外に表示されました。ただし、ボタンはタッチイベントを受信して​​いませんでした。ジャックの答えを使用して、ボタンのタッチイベントを検出できました:https : //stackoverflow.com/a/30431157/3344977

これがSwiftバージョンです:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    let translatedPoint = button.convertPoint(point, fromView: self)

    if (CGRectContainsPoint(button.bounds, translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, withEvent: event)
    }
    return super.hitTest(point, withEvent: event)
}

スウィフト4:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

    let translatedPoint = button.convert(point, from: self)

    if (button.bounds.contains(translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, with: event)
    }
    return super.hitTest(point, with: event)
}

それはまさに私のシナリオでした。もう1つのポイントは、このメソッドをCollectionViewCellクラス内に配置する必要があることです。
Hamid Reza Ansari

しかし、なぜオーバーライドされたメソッドにボタンを挿入するのでしょうか???
rommex

10

なぜこれが起こっているのですか?

これは、サブビューがスーパービューの境界の外にある場合、そのサブビューで実際に発生するタッチイベントがそのサブビューに配信されないためです。しかし、それはWILLそのスーパーに配信されます。

サブビューが視覚的にクリップされるかどうかに関係なく、タッチイベントは常にターゲットビューのスーパービューの境界の長方形を尊重します。つまり、スーパービューの境界の長方形の外側にあるビューの一部で発生するタッチイベントは、そのビューに配信されません。リンク

するべきこと?

スーパービューが上記のタッチイベントを受け取ったら、このサブイベントがこのタッチイベントを受け取るものであることをUIKitに明示的に通知する必要があります。

コードはどうですか?

スーパービューで、実装します func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) }
        // convert the point into subview's coordinate system
        let subviewPoint = self.convert(point, to: subview)
        // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
        if !subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
        return super.hitTest(point, with: event)
    }

魅力的なゴッチャ:「最高の小さすぎるスーパービュー」に行く必要があります

問題のビューが外側にある「最高」のビューに「上」に移動する必要があります。

典型的な例:

コンテナービューCの画面Sがあるとします。コンテナービューコントローラーのビューはVです(VはCの内側に配置され、同じサイズになります)。実際にはVの外側にあるビュー。

ただし、BもCの外側にあることに注意してください。

この例では、解決策を適用する必要がありoverride hitTest、実際にはCに、ではないVへ。Vに適用すると、何も起こりません。


4
これありがとう!私はこれに起因する問題に何時間も費やしてきました。これを投稿する時間を割いていただき本当にありがとうございます。:)
ClayJ 2018

@ScottZhu:私はあなたの答えに重要なゴチャを追加しました。もちろん、あなたは私の追加を削除したり変更したりしてお気軽に!再度、感謝します!
Fattie

9

迅速:

ここで、targetViewは、イベントを受信するビューです(全体または一部が親ビューのフレームの外側にある場合があります)。

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self)
        if CGRectContainsPoint(targetView.bounds, pointForTargetView) {
            return closeButton
        }
        return super.hitTest(point, withEvent: event)
}

5

(他の人のためのように)私にとっては、答えはしないで上書きするhitTest方法ではなく、pointInside方法を。これを2か所で行う必要がありました。

Superview これは、clipsToBoundstrueに設定した場合、この問題全体が消えるという見方です。これUIViewがSwift 3 の私のシンプルなコードです。

class PromiscuousView : UIView {
    override func point(inside point: CGPoint,
               with event: UIEvent?) -> Bool {
        return true
    }
} 

サブビュー マイレージは異なる場合がありますが、私の場合pointInside、タッチが範囲外であると判断したサブビューのメソッドも上書きする必要がありました。鉱山はObjective-Cなので、次のようになります。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    WEPopoverController *controller = (id) self.delegate;
    if (self.takeHitsOutside) {
        return YES;
    } else {
        return [super pointInside:point withEvent:event];
    }
}

この回答(および同じ質問に対する他のいくつかの回答)は、理解を深めるのに役立ちます。


2

swift 4.1が更新されました

同じUIViewでタッチイベントを取得するには、次のオーバーライドメソッドを追加するだけで、境界外に配置されたUIViewでタップされた特定のビューを識別します

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let pointforTargetview:CGPoint = self.convert(point, to: btnAngle)
    if  btnAngle.bounds.contains(pointforTargetview) {
        return  self
    }
    return super.hitTest(point, with: event)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.