web-dev-qa-db-ja.com

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

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

84
ThomasM

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

編集:例:

- (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];
    }

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

91
jnic

@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の境界の外側に移動できます。

ベスト

23
Summon

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

- (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];

}

この場合、ポイントがボタンの境界内にある場合、そこにコールを転送します。そうでない場合は、元の実装に戻ります。

19
Ja͢ck

私の場合、UICollectionViewCellを含むUIButtonサブクラスがありました。セルでclipsToBoundsを無効にすると、セルの境界の外側にボタンが表示されました。ただし、ボタンはタッチイベントを受信して​​いませんでした。ジャックの答えを使用して、ボタンのタッチイベントを検出できました。 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)
}
11
user3344977

迅速:

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)
}
8
Luke Bartolomeo

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

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

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

何をする必要がありますか?

スーパービューが上記のタッチイベントを受け取ると、サブビューがこのタッチイベントを受け取るサブビューであることを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があります。実際にVの外側にあるビュー.

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

この例では、ソリューションを適用する必要がありますoverride hitTest実際にはVではなくCに。 Vに適用すると、何もしません。

8
Scott Zhu

私(他の人)の場合、答えはhitTestメソッドをオーバーライドするnotではなく、pointInsideでした方法。私はこれをたった2か所でしなければなりませんでした。

Superviewこれは、clipsToBoundsがtrueに設定されている場合、この問題全体が消えるというビューです。 Swift 3:のシンプルなUIView in

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];
    }
}

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

3
Dan Rosenstark

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)
}
2
Ankit Kushwah