web-dev-qa-db-ja.com

iOS 7のジェスチャー認識機能の問題

画面にいくつかのUIViewオブジェクト(たとえば5)を追加します。これは、たとえば、view5.superview = view4view4.superview = view3view3.superview=view2view2.superview = view1。これらすべてに対してUIViewを設定しました。uitapgesturerecognizer; view1-4の場合はコールバックでNSLog(@ "tap%@"、self)を実行し、view5タップの場合は次のように設定します。view4を階層から削除し、同じオブジェクトview4 'を階層の同じ場所に配置します。このオブジェクトには、UITapGestureRecognizerが設定されているview5 'も含まれています(実際には、マークアップの一部を同様の部分に置き換えています)。

次に、view5をクリックします。一部の時間、view5はそのタップをキャッチし続け、すべてがOKですが、後でランダムな数のタップ(この数が異なるたび)に、view1-4オブジェクトの1つがこのタップをキャッチし始めますが、view5はクリックしています。問題全体にはランダムな性質があります-時にはそれは10回目の打ち上げで、時には2回目の打ち上げで発生します。間違ったオブジェクトが最初のタップでタップをキャッチすることがあります。また、すべてがうまくいかなかったときに、どのオブジェクトがタップをキャッチするかわかりません。 view(n + 1)のフレームは、たとえばフレームview(n)の半分として設定されましたが、view1のフレーム-たとえば(0,0 320、460)。

上記のuiオブジェクトを使用したすべての操作はメインスレッドで行われ、私がこれまでに説明したことはすべて、iOS 4.3〜6.1で完全に機能し、さらに複雑な例が含まれています。しかし、iOS7はそれを何らかのランダムな地獄から作り出します。

Update:デバッグプロセスを簡略化するために、サンプルプロジェクトを作成しました。タップ時にサブビューの追加/削除操作はありません。画面上のビューは4つだけです。アプリをタップすると、どのビューがタップされたかが記録されます。したがって、最小のビュー(4)をタップする必要があります。ログに「タップ4タップ4タップ4…」と表示された場合-これは、すべてが正常に動作し、停止して再度実行し、停止して再度実行し、停止して再度実行する場合などです。 +正常に実行された場合)最初の行に「タップ4」は表示されず、「タップ1」または「タップ2」または「タップ3」が表示され、続行されます-これらは悪いケースです。

サンプルプロジェクトはここからダウンロードできます: http://tech.octopod.com/test/BuggySample.Zip (ちょうど33 Kb))。

更新2

アップルにバグを投稿しました。フィードバックがありましたら、ここに投稿します。ただし、適切な回避策があれば幸いです。

更新3

Yuvrajsinhが提供するソリューションは、実際にサンプルプロジェクトに取り組んでいます。残念ながら、それはまだそれが最初に現れたメインプロジェクトで発生した問題を解決するのに役立ちません。現在の主な理由は、セルフジェスチャのないビューがクリック可能なコンテンツに配置されている場合、その下のランダムビュー要素がインタラクションをキャッチし始めることです(インタラクションジェスチャが設定された一番上のビューの代わりに)。更新されたサンプルはここからダウンロードできます: http://tech.octopod.com/test/BuggySample2.Zip

25
Kup

この問題はiOS 7でのみ発生しているため、新しいデリゲートメソッドの1つを使用して問題を解決できます。

_– gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
– gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:
_

_gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer_を実装し、ジェスチャーのビューのスーパービューを「クロール」することで解決しました。スーパービューのジェスチャーが提供されたものと等しい場合、「YES」を返すことができます。ここに私の完全な解決策の詳細を示します: https://stackoverflow.com/a/19659848/1147934

説明
iOS 7のジェスチャー認識機能の問題は、スーパービューのジェスチャーが受信サブビュージェスチャーの1つがタッチを受け取る前のタッチです。これにより、スーパービュージェスチャーが認識し、サブビューの認識機能がキャンセルされます。これは(不正解ですか)で、Appleに複数のバグが報告されています。 Appleは、ジェスチャーがタッチを受け取る順序を保証するものではないことが指摘されています。多くの「私たち」は、iOS 7で変更された実装の詳細に依存していると思います。これはこの問題に対処するのに役立つように設計されているように見える新しいデリゲートメソッドを使用する理由。

注:私は自分のサブクラス認識機能を使用して広範囲のテストを行い、すべてのタッチをログに記録しましたが、認識機能が失敗する理由は、スーパービュージェスチャがサブビューのジェスチャーは、ケースの約5%でした。これが起こるたびに、失敗が起こりました。これは、ジェスチャーの多い「深い」階層がある場合に頻繁に発生します。

新しいデリゲートメソッドは混乱を招く可能性があるため、注意深く読む必要があります。

私はメソッドを使用しています(理解しやすいように引数の名前を変更しました)

– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *) otherRecognizer

「YES」を返す場合、提供されるジェスチャー認識機能otherRecognizerは、認識される前にthisRecognizerが失敗することを要求します。これが、私の回答でスーパービュー階層をクロールして、otherRecognizerを持つスーパービューが含まれているかどうかを確認する理由です。そうである場合、otherRecognizerがサブビューにあり、スーパービューのジェスチャーが認識される前にshouldが失敗するため、thisRecognizerthisRecognizerの失敗を要求する必要があります。これにより、サブビュージェスチャーが確実に認識されますbeforeスーパービューのジェスチャーが認識されます。理にかなっていますか?

代替
私はそれを逆にして使用することができます:

– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherRecognizer

subview階層全体をクロールして、otherRecognizerが含まれているかどうかを確認し、含まれている場合はYESを返す必要があります。サブビュー階層全体をクロールすることは、スーパービュー階層をチェックするよりもはるかに難しく費用がかかるため、この方法は使用しません。単純なwhileループを使用してスーパービューの階層をチェックすることができますが、サブビュー階層のクロールは再帰的な関数でなければなりません。したがって、私が概説する最初のアプローチをお勧めします。

警告!
_gestureRecognizer:shouldReceiveTouch:_の使用には注意してください。問題は、どのジェスチャーが最初にタッチを受け取る(他のジェスチャーをキャンセルする)かという問題です...これは競合解決の問題です。 _gestureRecognizer:shouldReceiveTouch:_を実装すると、サブビュージェスチャーのときにguessする必要があるため、サブビュージェスチャーが失敗した場合にスーパービューのジェスチャーを拒否するリスクがありますかもしれない認識される。サブビュージェスチャは、タッチが範囲外であること以外の理由で正当に失敗する可能性があるため、正しく推測するために実装の詳細を知る必要があります。あなたwantサブビュージェスチャーが失敗したときに認識されるスーパービュージェスチャーですが、実際に失敗する前に失敗するかどうかを確実に知ることはできません。サブビュージェスチャーが失敗した場合、通常はスーパービュージェスチャーに認識させます。これはnormalレスポンダーチェーン(サブビュースーパービュー)であり、これをいじると、予期しない動作が発生する可能性があります。

19
Aaron Hayman

私はあなたのコードにいくつかの変更を加えました、そして私はそれをたくさんテストしました、そして問題は発生していません。

ビューを作成するときに、各ビューにタグを設定して区別します。

View1234 *v1 = [[View1234 alloc] initWithId:@"1"];
v1.tag =1;
v1.backgroundColor = [UIColor redColor];
v1.frame = CGRectMake(0, 0, 320, 460);

View1234 *v2 = [[View1234 alloc] initWithId:@"2"];
v2.tag=2;
v2.backgroundColor = [UIColor greenColor];
v2.frame = CGRectMake(0, 0, 160, 230);

View1234 *v3 = [[View1234 alloc] initWithId:@"3"];
v3.tag=3;
v3.backgroundColor = [UIColor blueColor];
v3.frame = CGRectMake(0, 0, 80, 115);

View1234 *v4 = [[View1234 alloc] initWithId:@"4"];
v4.tag=4;
v4.backgroundColor = [UIColor orangeColor];
v4.frame = CGRectMake(0, 0, 40, 50);

View1234.h

@interface View1234 : UIView <UIGestureRecognizerDelegate> {
    NSString *vid;
}

- (id) initWithId:(NSString *)vid_;
@end

そして以下はView1234.mのコード全体です

- (id) initWithId:(NSString *)vid_ {
    if (self = [super init]) {

        vid = [vid_ retain];

        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init];
        tapGesture.delegate = self;
        tapGesture.numberOfTapsRequired = 1;
        tapGesture.numberOfTouchesRequired = 1;
        [tapGesture addTarget:self action:@selector(handleTap:)];

        [self addGestureRecognizer:tapGesture];

        [tapGesture release];

    }

    return self;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
    if (touch.view==self ) {
        return YES;
    }
    else if ([self.subviews containsObject:touch.view] && ![touch.view isKindOfClass:[self class]])
    {
        return YES;
    }
    return NO;
}

/*- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{

    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        if (self.tag==gestureRecognizer.view.tag) {
            return YES;
        }
    }

    return NO;
}*/

- (void) handleTap:(UITapGestureRecognizer *)tap {
    NSLog(@"tap %@", vid);
}

- (void) dealloc {

    [vid release];
    vid = nil;

    [super dealloc];
}

[〜#〜] update [〜#〜]:この問題が実際に発生する理由.

UIViewを別のUIViewのサブビューとして各ビューにUITapGestureRecognizerを付けて追加すると、まれにUITapGestureRecognizer状態がFailedどういうわけか(50回以上デバッグして、これを知った)。したがって、任意のビューのサブビューがタップジェスチャーを処理できない場合、システムはジェスチャーをスーパービューに渡してそのジェスチャーを処理し、これが続行されます。

デバッグすると、ビューの階層ごとにgestureRecognizerShouldBeginが複数回呼び出されることがわかります。この特定のケースでは、view3をタップすると、view3がビュー階層の第3レベルにあるため、gestureRecognizerShouldBeginを呼び出すため、gestureRecognizerShouldBeginview3, view2 and view1に対して呼び出されます。

したがって、問題を解決するために、正しいビューの場合はYESフォームgestureRecognizerShouldBeginを返し、残りの場合はNOを返して、問題を解決します。

PDATE 2:編集した回答のコードに変更を加えました。問題が解決されることを願っています。また、@ masmorのおかげで、問題を解決するための彼の答えからいくつかの手掛かりも見つかりました。

12
Yuvrajsinh

認識エンジンにデリゲートを設定し、gestureRecognizer:shouldReceiveTouch:を実装します。

実装は基本的にサブビューへのタッチをブロックする必要がありますが、実際のビューの階層と設定に応じておそらくいくつかの追加の基準があります。

以下のサンプルは、ヒットビューが直接サブビューであるかどうか、およびジェスチャ認識機能があるかどうかを単にチェックします。この場合、タッチの表示が許可されます。

不要なビューが認識機能を起動する可能性がないため、ブロックタッチは認識機能の状態をいじるよりも堅牢である必要があります。カスタム条件を実装する要件は欠点ですが、この場合のように原因不明のバグを回避する場合は、動作を明示的に実装する方が堅牢だと思います。

#import "View1234.h"

@interface View1234 () <UIGestureRecognizerDelegate>

@end

@implementation View1234

- (id) initWithId:(NSString *)vid_ {
    if (self = [super init]) {

        vid = [vid_ retain];

        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init];
        tapGesture.numberOfTapsRequired = 1;
        tapGesture.numberOfTouchesRequired = 1;
        [tapGesture addTarget:self action:@selector(handleTap:)];

        tapGesture.delegate = self;

        [self addGestureRecognizer:tapGesture];

        [tapGesture release];
    }

    return self;
}

//- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
//    
//    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
//        if (self.tag==gestureRecognizer.view.tag) {
//            return YES;
//        }
//    }
//    
//    return NO;
//}

- (void) handleTap:(id)tap {
    NSLog(@"tap %@", vid);
}

- (void) dealloc {

    [vid release];
    vid = nil;

    [super dealloc];
}

- (BOOL)shouldReceiveTouchOnView:(UIView *)hitView {
  NSLog(@"Should view:\n%@\nreceive touch on view:\n%@", self, hitView);

  // Replace this implementation with whatever you need...
  // Here, we simply check if the view has a gesture recognizer and
  // is a direct subview.
  BOOL res = (hitView.gestureRecognizers.count == 0 &&
              [self.subviews containsObject:hitView]);

  NSLog(@"%@", res? @"YES":@"NO");

  return res;
}

#pragma mark - Gesture Recognizer Delegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch {
  UIView *hitView = [self hitTest:[touch locationInView:self.superview]
                        withEvent:nil];
  if (hitView == self) {
    NSLog(@"Touch not in subview");
    return YES;
  }

  return [self shouldReceiveTouchOnView:hitView];
}

@end
2
voxlet

私はあなたのプロジェクトまたは以下を試していません。

ビューに触れたときにgestureRecognizerShouldBegin:を使用して、ビューに属さないジェスチャが発生しないようにする必要があります。

これをUIViewのサブクラスで行うか、またはUIViewに(プロパティまたは関連付けられたオブジェクトを使用して)カテゴリを作成し、各ビューインスタンスが何を行うかを決定するフラグを追加することができます。一部のビュータイプが壊れるので注意してください。

問題がビューの順序である場合、それは役に立ちません...

0
Wain