UITapGestureRecognizerを持つビュー内のUIButton


184

との見解がありUITapGestureRecognizerます。ビューをタップすると、このビューの上に別のビューが表示されます。この新しいビューには3つのボタンがあります。これらのボタンの1つを押すと、ボタンアクションが得られず、タップジェスチャーアクションしか得られません。そのため、これらのボタンを使用できなくなりました。これらのボタンにイベントを通過させるにはどうすればよいですか?奇妙なことに、ボタンはまだハイライトされています。

UITapGestureRecognizerをタップの受信後に削除することはできません。新しいビューも削除できるためです。フルスクリーンビデオコントロールのような動作が必要なことを意味します

回答:


256

コントローラまたはビュー(ジェスチャ認識機能を作成する方)をのデリゲートとして設定できますUITapGestureRecognizer。次に、デリゲートで実装できます-gestureRecognizer:shouldReceiveTouch:。実装では、タッチが新しいサブビューに属しているかどうかをテストでき、属している場合は、それを無視するようにジェスチャー認識機能に指示できます。次のようなもの:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}

ヘッダーファイルでは、ビューにUIGestoreRegognizerDelegateを実装し、.miには上記のコードを追加しました。タップはこのメソッドに入ることはなく、ハンドラに直接行きます。何か案は?
kmehta 2011

1
@kmehtaは、UIGestureRecognizerデリゲートプロパティを設定するのを忘れた可能性があります。
まで

この回答を一般化して、IBActionが関与するすべてのケースを処理できますか?
Martin Wickman、2011

@Martin IBActionはにコンパイルされるvoidため、実行時に検出に使用する情報が残りません。ただし、できることは、ビュー階層を上に移動し、をテストしUIControlNOコントロールが見つかった場合は戻ることです。
リリーバラード

これのthx、それは私を助けます。ボタンがUIImageViewにある場合は、imageViewのuserInteractionEnabledがYESに設定されていることを確認してください。
james075

156

ケーシーのフォローアップとしてケビン・バラードの回答へのフォローアップとして:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

これは基本的に、ボタン、スライダーなどのユーザー入力タイプのコントロールすべてを機能させます。


1
これはsetCancelsTouchesInView = NO、コントロールとの対話時にタップジェスチャーをトリガーしないようにすることができる柔軟なソリューションです。しかし、あなたはそれをよりよく書くことができます:return ![touch.view isKindOfClass:[UIControl class]];
griga13

Swift 4+ソリューション:return!(touch.view is UIControl)
nteissler

103

この回答はここにあります:リンク

あなたも使うことができます

tapRecognizer.cancelsTouchesInView = NO;

これは、タップ認識機能がすべてのタップをキャッチする唯一のものになるのを防ぎます

UPDATE - マイケルは、このプロパティを記述したドキュメントへのリンクを述べた:cancelsTouchesInView


8
これは、受け入れられた回答IMOになるはずです。これにより、すべてのサブビューが独自にタップを処理できるようになり、タブ認識機能が接続されているビューがタップを「ハイジャック」するのを防ぎます。ビューをクリック可能にしたい場合は、デリゲートを実装する必要はありません(最初のレスポンダーを辞任する/テキストフィールドのキーボードを非表示にするなど)。
EeKay

4
これは間違いなく受け入れられる答えになるはずです。この答えにより、Appleのドキュメントを適切に読むようになりました。これにより、ジェスチャー認識機能が、サブビューが認識されたイベントを取得しないようにします。
Michael van der Westhuizen 2012

1
これは以前は機能していましたが、今は機能しません。おそらく、ボタンの下にscrollViewがあるためでしょうか。上記の回答のようにデリゲートを実装する必要がありました。
シスバーグ

7
少し実験したところ、ジェスチャレコグナイザを含むビューがUIControlの兄弟である場合にのみ機能し、ビューがUIControlの親である場合には機能しないことに気付きました。
xissburg

6
実際には意図したとおりに動作しません...はい、UIのイベントをタップ認識機能が食べないようにします。ただし、それでも認識アクションが実行されます。これは同時に2つのアクションを実行します。
Tek Yin

71

ケビン・バラードの回答のフォローアップとして、私はこれと同じ問題を抱えていて、結局このコードを使用しました:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([touch.view isKindOfClass:[UIButton class]]){
        return NO;
    }
    return YES;
}

同じ効果がありますが、これは任意のビュー深度で任意のUIButtonで機能します(私のUIButtonはいくつかのビューの深さで、UIGestureRecognizerのデリゲートはそれを参照していませんでした)。


6
ここでディックになりたくないですが、それは本当にイエスとノーです。Cocoa規約を使用してください。TRUE / FALSE / NULLはCoreFoundation-Layerの場合です。
2010

私が読んだものから、YESとNOは読みやすさのために実際に作成されました。読みやすさは主観的です。それは、誰もが過度に心配しているわけではないメソッドとプロパティの命名規則に由来しています。命名規則を厳密に遵守したい場合は、どのNSWhateverにもYESとNOを使用します。ただし、開発者を調査すると、どちらがより読みやすいかについて、さまざまな意見が得られると私は言います[ボタンsetHidden:YES]。-または-[ボタンsetHidden:TRUE];
Pimp Juice McJones

1
私が言いたいのは、命名規則の前提が読みやすさである場合、場合によっては主観的であることを否定するのは難しいと思います。あなたが純粋主義者なら、あなたはその声明を理解しません。
Pimp Juice McJones、2011

2
それは主観的に思えるかもしれませんが、Cocoaプログラミングのしばらく後には、より意味的に正確なコードフローに実際に入る... Objective-Cの数年後、「TRUE」は本当に「隠れた」ものとあまり一致しないため、本当に間違っているように見えます。上手。
Kendall Helmstetter Gelner 2012

タッチジェスチャーをUIButtonにTouch Up Inside UITouchイベント(後者はUIButtonをタップすることによって生成されるイベントです)としていつ渡すことができますか?既にタップジェスチャに関連付けられている15のタッチイベントを持つボタンのタップジェスチャレコグナイザを作成することは、重複しており、正しくないようです。推測ではなく、コードを見たいのですが。
James Bush、

10

iOS 6.0以降では、デフォルトの制御アクションにより、ジェスチャー認識機能の動作が重複するのを防ぎます。たとえば、ボタンのデフォルトアクションはシングルタップです。ボタンの親ビューにシングルタップジェスチャレコグナイザが接続されていて、ユーザーがボタンをタップした場合、ボタンのアクションメソッドはジェスチャレコグナイザの代わりにタッチイベントを受け取ります。これは、コントロールのデフォルトアクションと重複するジェスチャ認識にのみ適用されます。

AppleのAPIドキュメントから


8

これらの回答は不完全でした。このブール演算の使用方法については、複数の投稿を読む必要がありました。

* .hファイルにこれを追加します

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

* .mファイルにこれを追加します

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}

7

ここからそれを行う別の方法を見つけまし。各ボタンの内側にあるかどうかにかかわらず、タッチを検出します。

(1)pointInside:withEvent: (2) locationInView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}

私はこれについて他のすべての解決策を試しましたが、-pointInside:withEvent:アプローチが私のために働いた唯一のものでした。
ケニーワイランド2014

3

これは私のために働いたリリーバラードの回答のSwiftバージョンです:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}

3

スウィフト5

スーパービューのボタンをタップジェスチャーで

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

私の場合、hitTestの実装がうまくいきました。ボタン付きのコレクションビューがありました

このメソッドは、point(inside:with:)各サブビューのメソッドを呼び出してビュー階層をたどり、タッチイベントを受け取るサブビューを決定します。場合はpoint(inside:with:)trueを返し指定された点を含む最前面のビューが発見されるまで、そのサブビューの階層は、同様に横断されます。

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}

1

次のブール値を設定することにより、UITapGestureRecognizerが他のイベント(ボタンのタップなど)をキャンセルしないようにすることができます。

    [tapRecognizer setCancelsTouchesInView:NO];

1

シナリオが次のような場合:

単純なビューと、そのビューにサブビューとして追加されたいくつかのUIButtons、UITextFieldコントロールがあります。ここで、コントロール(追加したサブビュー)を除いて、ビューの他の場所をタッチしたときにキーボードを非表示にしたいとします。

次に、ソリューションは次のとおりです。

次のメソッドをXYZViewController.m(ビューがある)に追加します

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}

最初に@cdasherを試してみましたの答えを、私の場合にも、ボタンをクリックの上、IAMが取得touch.viewを私の『UIButton』コントロールのViewControllerに私の『ビュー』としてではなく。タップのビュープロパティがタップが発生した元のビューを返さない理由を誰かが言うことができます
NaveenRaghuveer 2013年

1
スクロールイベントまたはタッチイベントを要求するその他のビューがある場合は機能しません。
lkraider

1

cdasherの答えを最適化すると、

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.