web-dev-qa-db-ja.com

UIGestureを背後のビューに転送する

Iphone(iOS 4.0以降)アプリで作業していますが、複数のビュー間でのタッチ処理に問題があります。私はこのようなビュー構造を持っています

---> A superView 
     |
     ---> SubView - A 
     |
     ---> SubView - B (exactly on top of A, completely blocking A).

enter image description here

基本的に、スーパービューと兄弟サブビューAとBがあります。BはAと同じフレームを持っているため、Aを完全に非表示にします。

今、私の要件はこれです。

  1. SubView Bはすべてのスワイプおよびタップ(シングルおよびダブル)ジェスチャーを受信する必要があります。
  2. SubView Aはすべてのピンチジェスチャを受信する必要があります。

これが私がビューにジェスチャー認識を追加した方法です

UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
[leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
leftSwipe.delegate  =   self;
[bView addGestureRecognizer:leftSwipe];
[leftSwipe release];

UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
[rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
rightSwipe.delegate   =   self;
[bView addGestureRecognizer:rightSwipe];
[rightSwipe release];

UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
pinch.delegate    =   self;
[aView addGestureRecognizer:pinch];
[pinch release];

私はいくつかの研究を行い、UIGestureRecognizerDelegateは有望に見え、デリゲートメソッドを実装しました

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

基になるビューがこれらのイベントを取得することを期待して、SubView Bに対してNOを返しました。そんな運はない

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] && [gestureRecognizer view] == bView) {
        NSLog(@"pinchGesture");
        //bView.userInteractionEnabled = NO;
        return NO;
    }
    return YES;
}

次に、デリゲートコールバック内のSubView Bのユーザーインタラクションを無効にしました(上記のブロックのコメントコード)。ピンチジェスチャが認識されると、ジェスチャの残りの部分がSubView Aによって受信されることを期待しています。

それが今私が立っているところです。 this の質問を見ましたが、受け入れられた答えにはcancelsTouchesInViewプロパティが含まれます。ただし、cancelsTouchesInViewは特定のタッチイベントをキャンセルするだけで、転送しないと考えています。

要件を達成する他の方法はありますか?あなたが提供するあらゆるヒントに取り組む準備ができています。

編集:バウンティタイム

私のいわゆるsubView Aは、実際にはサードパーティライブラリのViewクラスのインスタンスであり、すべてのタッチを取り去り、その上でジェスチャーを制御することはできません。左右のスワイプに異なる実装が必要であり、ピンチ、タップなど、このサードパーティのビューで機能するのと同じように機能する必要があります。そこで、A(SubView B)の上にビューを置いて、左右にスワイプします。しかし今、私は他のジェスチャーイベントを基礎となるライブラリに転送したいと考えています。

57
Krishnabhadra

問題を正しく理解できれば、AおよびBビューと同じように、Rectで別の明確なビューを追加し、その上にすべてのジェスチャーを実装することができます:ピンチジェスチャーを行うとき、スワイプしてタップするとき(単一double)ジェスチャ-subView Bを制御します。さまざまな方法で実行できます。ポインタを使用するか、受信したジェスチャをクラスのメソッドに送信するだけで、サブビューを制御できます。

例えば:

UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
[leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
leftSwipe.delegate  =   subViewAcontroller;
[clearView addGestureRecognizer:leftSwipe];
[leftSwipe release];

UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
[rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
rightSwipe.delegate   =   subViewAcontroller;
[clearView addGestureRecognizer:rightSwipe];
[rightSwipe release];

UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
pinch.delegate    =   subViewBcontroller;
[clearView addGestureRecognizer:pinch];
[pinch release];

または:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
        NSLog(@"pinchGesture");
        [subViewBcontroller solvePinchGesture: gestureRecognizer];
    }
//etc
    return YES;
}
18
SentineL

表示階層が正しくないため、ピンチジェスチャはaViewで受信されません。

---> A superView 
 |
 ---> SubView - A 
 |
 ---> SubView - B (exactly on top of A, completely blocking A).

bViewは、ピンチジェスチャを含むすべてのマルチタッチをキャプチャしますが、bViewはピンチジェスチャを処理しないため、Responderチェーンの次のレスポンダー、つまりbViewのsuperViewまたはbViewのviewControllerに渡します。 pinchイベントは、兄弟ビューに並列に渡されるため(親子関係もview-viewController関係もないため)、兄弟ビューに渡されることはありません。

以下のようにビュー階層を変更できます。

---> A superView 
 |
 ---> SubView - A 
       |
       ---> SubView - B (child view of b, exactly on top of A, completely blocking A)



- (void)viewDidLoad {
    [super viewDidLoad];

    aView = [[UIView alloc] initWithFrame:self.view.bounds];
    aView.backgroundColor = [UIColor blueColor];

    bView = [[UIView alloc] initWithFrame:self.view.bounds];
    bView.backgroundColor = [UIColor redColor];

    [self.view addSubview:aView];
    [aView addSubview:bView];

    UISwipeGestureRecognizer *leftSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
    [leftSwipe setDirection:(UISwipeGestureRecognizerDirectionLeft)];
    leftSwipe.delegate  =   self;
    [bView addGestureRecognizer:leftSwipe];

    UISwipeGestureRecognizer *rightSwipe  =  [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
    [rightSwipe setDirection:(UISwipeGestureRecognizerDirectionRight)];
    rightSwipe.delegate   =   self;
    [bView addGestureRecognizer:rightSwipe];

    UIPinchGestureRecognizer *pinch   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
    pinch.delegate    =   self;
    [aView addGestureRecognizer:pinch];
}

以下のリンクから詳細を入手してください: https://developer.Apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/EventsiPhoneOS/EventsiPhoneOS.html#//Apple_ref/doc/uid/TP40009541 -CH2-SW1

->レスポンダーチェーンを通過するイベントに関する重要な注意「システムがタッチイベントを配信すると、最初に特定のビューに送信されます。タッチイベントの場合、そのビューはhitTest:withEvent :;によって返されます。 ”-モーションイベント、リモートコントロールイベント、アクションメッセージ、および編集メニューメッセージ、そのビューは最初のレスポンダーです。初期ビューがイベントを処理しない場合、特定のパスに沿ってレスポンダーチェーンを上に移動します。

  1. ヒットテストビューまたはファーストレスポンダーは、イベントまたはメッセージが存在する場合、そのビューコントローラーに渡します。ビューにView Controllerがない場合、イベントまたはメッセージをスーパービューに渡します。
  2. ビューまたはそのView Controllerがイベントまたはメッセージを処理できない場合、ビューのスーパービューに渡します。
  3. 階層内の後続の各スーパービューは、イベントまたはメッセージを処理できない場合、最初の2つのステップで説明したパターンに従います。
  4. ビュー階層の最上位のビューは、イベントまたはメッセージを処理しない場合、処理のためにウィンドウオブジェクトに渡します。
  5. UIWindowオブジェクトは、イベントまたはメッセージを処理しない場合、シングルトンアプリケーションオブジェクトに渡します。」

上記の私の答えは唯一の解決策ではありません。あなたがする必要があるのは、同じレスポンダーチェーンでスワイプとピンチジェスチャーの処理ロジックを作成することです。そのため、未処理のピンチジェスチャーイベントが正しいレスポンダーに渡されます。たとえば、サブビューbを省略して、スワイプ左/右ジェスチャーをaViewのsuperViewに追加できます。

15
flypig

少し前に似たようなことをしました。私は1本の指と2本以上の指で2つの異なることをしたかったのです。私のソリューションはあなたのためにいくつかの微調整を必要とするかもしれません、そして、私はすべてがあなたのコード環境でまったく同じように働くことを保証することができません。 hitTestの適切なドキュメントと、それが連続して複数回呼び出される理由は見つかりませんでした。だから私はいくつかのテストを行い、1本と複数本の指のジェスチャーをどのように分離するかという解決策に至りました。

私はこのビュー階層を持っていました:

---> A superView 
 |
 ---> ScrollView - A - all touches
 |
 ---> ScrollView - B - only two+ finger scroll (exactly on top of A, completely blocking A).

SuperViewのhitTestにスイッチを実装しました。

BOOL endDrag; // indicates scrollviewB did finish
BOOL shouldClearHitTest; // bool to help calling the method clearHitTest only once.
BOOL multiTouch; // needed for the method clearHitTest to know if a multi-touch is detected or not
NSMutableArray *events; // store all events within one eventloop/touch;


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!endDrag) {
        if (!shouldClearHitTest) { // as hitTest will be called multible times in one event-loop and i need all events, i will clean the temporaries when everything is done
            shouldClearHitTest = YES;
            [[NSRunLoop mainRunLoop] performSelector:@selector(clearHitTest) target:self argument:nil order:1 modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
        }
        [events addObject:event]; // store the events so i can access them after all hittests for one touch are evaluated

        if (event.type == UIEventTypeTouches && ([_events count] > 3 || [[event allTouches] count] > 0 || _currentEvent)) { // two or more fingers detected. at least for my view hierarchy
            multiTouch = YES;
            return scrollViewB;
        }
    }else {
        endDrag = NO;
    }
    return scrollViewA;
}

- (void)clearHitTest {
    if (shouldClearHitTest) {
        shouldClearHitTest = NO;
        [events removeAllObjects];
        if (multiTouch) {
           // Do some special stuff for multiTouch
        }
        multiTouch = NO;
    }
}
1
Jonathan Cichon

BViewにピンチジェスチャがある場合。 aViewのピンチジェスチャアクションが呼び出された場合、bViewのピンチジェスチャアクションから...

UIPinchGestureRecognizer *pinchA   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinchA:)];
[aView addGestureRecognizer:pinchA];
[pinchA release];    

UIPinchGestureRecognizer *pinchB   =  [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinchB:)];
[bView addGestureRecognizer:pinchB];
[pinchB release];


-(void)handlePinchA:(UIGestureRecognizer*)gestureRecognizer
{
NSLog(@"handlePinch on A");  
}

-(void)handlePinchB:(UIGestureRecognizer*)gestureRecognizer
{
NSLog(@"handlePinch on B");
[self handlePinchA:gestureRecognizer];  
}

aViewはthirdParty libです。外部からhandlePinchAメソッドを呼び出すことができますか? NSLog gestureRecognizers私を印刷しますaction = handlePinchA

NSLog(@"gestureRecognizers %@", [aView gestureRecognizers]);

私を印刷します:

gestureRecognizers (
"<UIPinchGestureRecognizer: 0x8b3e500; state = Possible; view = <UIView 0x8b2f470>; target= <(action=handlePinchA:, target=....
0

必要に応じて、以下のコード行を使用することをお勧めします。

//--->for bview gesture to detect swipe
[pinch requireGestureRecognizerToFail:leftSwipe]; && 
[pinch requireGestureRecognizerToFail:rightSwipe];

//--->for aview gesture to detect pinch
[rightSwipe requireGestureRecognizerToFail:rightSwipe]; ||
[leftSwipe requireGestureRecognizerToFail:pinch];
0
Kuldeep