web-dev-qa-db-ja.com

UIScrollViewとそのサブビューの両方がタッチに応答することを許可します

UIScrollViewとそのサブビューの両方が、サブビュー内のすべてのタッチイベントを受け取るようにします。それぞれが独自の方法で応答できます。

または、タップジェスチャがサブビューに転送された場合、すべてうまくいきます。

多くの人々がこの一般的な分野で苦労しています。関連する多くの質問のいくつかを次に示します。

IScrollViewはどのようにサブビューからタッチを盗みますか
IScrollViewからタッチを盗む方法
IScrollViewでスクロールをキャンセルする方法

ちなみに、スクロールビューでhitTest:withEvent:をオーバーライドすると、userInteractionEnabledがYESである限りタッチが表示されます。しかし、それは私の問題を実際に解決しません:

1)その時点では、タップかどうかはわかりません。
2)時々、userInteractionEnabledをNOに設定する必要があります。

編集:はい、明確にするために、私はタップを鍋とは別に扱いたいです。タップはサブビューで処理する必要があります。パンは、通常の方法でスクロールビューで処理できます。

22

まず、免責事項。 userInteractionEnabledNOUIScrollViewに設定すると、タッチイベントはサブビューに渡されません。私の知る限り、例外は1つあります。例外はUIScrollViewのスーパービューでタッチイベントをインターセプトし、特にこれらのイベントをUIScrollViewのサブビューに渡すことです。正直に言うと、なぜあなたがこれをしたいのか分かりません。特定のUIScrollView機能(...スクロールなど)を無効にしたい場合は、UserInteractionを無効にせずに十分に簡単に実行できます。

あなたの質問を理解した場合、サブビューに渡されるUIScrollViewandでタップイベントを処理する必要がありますか?いずれにせよ(ジェスチャーが何であれ)、あなたが探しているのはプロトコルメソッド gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: プロトコルUIGestureRecognizerDelegateです。サブビューでは、ジェスチャレコグナイザーに関係なく、ジェスチャレコグナイザーにデリゲート(おそらく最初にUIGestureReconginzerを設定するクラス)を設定します。上記のメソッドをオーバーライドして、YESを返します。これで、このジェスチャは、ジェスチャを「盗んだ」可能性のある他のレコグナイザ(あなたの場合はタップ)とともに認識されます。この方法を使用すると、サブビューに特定の種類のジェスチャーのみを送信するようにコードを微調整したり、特定の状況でのみジェスチャーを送信したりすることもできます。それはあなたに多くの制御を与えます。メソッド、特にこの部分について必ず読んでください:

このメソッドは、gestureRecognizerまたはotherGestureRecognizerによるジェスチャの認識が、他のジェスチャ認識エンジンによるジェスチャの認識をブロックするときに呼び出されます。 YESを返すと、同時認識が保証されることに注意してください。一方、NOを返すと、他のジェスチャ認識エンジンのデリゲートがYESを返す可能性があるため、同時認識を防止することは保証されません。

もちろん、注意点があります:これはジェスチャー認識にのみ適用されます。そのため、touchesBegan:touchesEndedなどを使用してタッチを処理しようとすると、依然として問題が発生する可能性があります。もちろん、hitTest:を使用して生のタッチイベントをサブビューに送信できますが、なぜですか? UIViewをビューにアタッチしてすべての機能を無料で入手できるのに、UIGestureRecognizerのこれらのメソッドを使用してイベントを処理するのはなぜですか?標準のUIGestureRecognizerが提供できない方法でタッチを処理する必要がある場合は、subclassUIGestureRecognizerを使用してタッチを処理します。そうすれば、独自のカスタムタッチ処理とともにUIGestureRecognizerのすべての機能を利用できます。開発者がUIGestureRecognizerで使用するカスタムタッチ処理コードのほとんど(すべてではないにしても)を置き換えるために、AppleはUIViewを対象としています。 -再利用し、どのコードがどのタッチイベントを処理するかを緩和するときの対処がはるかに簡単になります。

38
Aaron Hayman

これがあなたに役立つかどうかはわかりませんが、スクロールビューでダブルタップを処理し、シングルタップをサブビューに転送したいという同様の問題がありました。 CustomScrollViewで使用されるコードは次のとおりです

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch* touch = [touches anyObject];
    // Coordinates
    CGPoint point = [touch locationInView:[self.subviews objectAtIndex:0]];

    // One tap, forward
    if(touch.tapCount == 1){
        // for each subview
        for(UIView* overlayView in self.subviews){
            // Forward to my subclasss only
            if([overlayView isKindOfClass:[OverlayView class]]){
                // translate coordinate
                CGPoint newPoint = [touch locationInView:overlayView];
                //NSLog(@"%@",NSStringFromCGPoint(newPoint));

                BOOL isInside = [overlayView pointInside:newPoint withEvent:event];
                //if subview is hit
                if(isInside){
                    Forwarding
                    [overlayView touchesEnded:touches withEvent:event];
                    break;
                }
            }
        }

    }
    // double tap : handle zoom
    else if(touch.tapCount == 2){

        if(self.zoomScale == self.maximumZoomScale){
            [self setZoomScale:[self minimumZoomScale] animated:YES];
        } else {
            CGRect zoomRect = [self zoomRectForScrollView:self withScale:self.maximumZoomScale withCenter:point];            

            [self zoomToRect:zoomRect animated:YES];
        }

        [self setNeedsDisplay];

    }
}

もちろん、有効なコードを変更する必要がありますが、この時点で、イベントを転送する必要があるかどうかを判断するために必要なすべての情報が必要です。 touchesMoved:withEvent:として別のメソッドでこれを実装する必要があるかもしれません。

これが役立つことを願っています。

3
Oyashiro

私はこれと同じ問題を抱えていましたが、スクロールビューがUIPageViewControllerの中にあったため、少し異なる方法で処理する必要がありました。

cancelsTouchesInViewの各認識エンジンのUIScrollViewプロパティをfalseに変更することで、UIPageViewController内のボタンへのタッチを受け取ることができました。

このコードをviewDidLoadに追加することでそうしました:

guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else {
     print("No gesture recognizers on scrollview.")
     return
}

for recognizer in recognizers {
    recognizer.cancelsTouchesInView = false
}
3
Hunter Monk

タッチとスクロールの違いが必要な場合は、タッチが移動したかどうかをテストできます。これがタップの場合、touchHasBeenMovedは呼び出されず、これがタッチであると想定できます。

この時点で、ムーブメントが発生したかどうかを示すブール値を設定し、このブール値を他のメソッドの条件として設定できます。

私は外出中ですが、それがあなたが必要とするものであるならば、私は後でよりよく説明することができます。

0
shannoga

100%正確ではなく、目的を達成するためのハック的な方法は、UIWindowをサブクラス化し、 void)sendEvent:(UIEvent *)event;をオーバーライドすることです。

簡単な例:

secondResponderWindow.hヘッダー内

//SecondResponderWindow.h

@protocol SecondResponderWindowDelegate
- (void)userTouchBegan:(id)tapPoint onView:(UIView*)aView;
- (void)userTouchMoved:(id)tapPoint onView:(UIView*)aView;
- (void)userTouchEnded:(id)tapPoint onView:(UIView*)aView;
@end

@interface SecondResponderWindow : UIWindow
@property (nonatomic, retain) UIView *viewToObserve;
@property (nonatomic, assign) id <SecondResponderWindowDelegate> controllerThatObserves;
@end

secondResponderWindow.mで

//SecondResponderWindow.m

- (void)forwardTouchBegan:(id)touch onView:(UIView*)aView {
    [controllerThatObserves userTouchBegan:touch onView:aView];
}
- (void)forwardTouchMoved:(id)touch onView:(UIView*)aView {
    [controllerThatObserves userTouchMoved:touch onView:aView];
}
- (void)forwardTouchEnded:(id)touch onView:(UIView*)aView {
    [controllerThatObserves userTouchEnded:touch onView:aView];
}

- (void)sendEvent:(UIEvent *)event {
    [super sendEvent:event];

    if (viewToObserve == nil || controllerThatObserves == nil) return;

    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    if ([touch.view isDescendantOfView:viewToObserve] == NO) return;

    CGPoint tapPoint = [touch locationInView:viewToObserve];
    NSValue *pointValue = [NSValue valueWithCGPoint:tapPoint];

    if (touch.phase == UITouchPhaseBegan)
        [self forwardTouchBegan:pointValue onView:touch.view];
    else if (touch.phase == UITouchPhaseMoved)
        [self forwardTouchMoved:pointValue onView:touch.view];
    else if (touch.phase == UITouchPhaseEnded)
        [self forwardTouchEnded:pointValue onView:touch.view];
    else if (touch.phase == UITouchPhaseCancelled)
        [self forwardTouchEnded:pointValue onView:touch.view];
}

2番目のレスポンダービューは-touchDidBegin:などを介してネイティブにタッチイベントを処理せず、SecondResponderWindowDelegateを実装する必要があるため、100%が期待したものに準拠していません。ただし、このハックを使用すると、追加のレスポンダーでのタッチイベントを処理できます。

このメソッドは、MITHIN KUMARの---(TapDetectingWindow に触発されて拡張されています。

0
xingzhi.sg