web-dev-qa-db-ja.com

UIPageViewControllerジェスチャーレコグナイザー

私は今しばらくの間アプリケーションに取り組んでいますが、NDAのためにこの質問をすることができませんでした。

ViewControllerでUIPageViewControllerをロードしています。 View Controllerにはボタンがあり、PageViewControllersジェスチャ認識エンジンによってオーバーライドされます。たとえば、ViewControllerの右側にボタンがあり、ボタンを押すと、PageViewControllerがページを引き継いで変更します。

PageViewControllerでボタンがタッチを受信し、ジェスチャー認識機能をキャンセルするにはどうすればよいですか?

PageViewControllerは、ViewControllerをそのビューのサブビューにすると思います。

すべてのジェスチャーをオフにできることは知っていますが、これは私が探している効果ではありません。

prefer PageViewControllerをサブクラス化しないAppleは、このクラスはサブクラス化することを意図していないと言います。

54
Rich86man

オーバーライドできます

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

pageViewControllerがタッチを受信するタイミングを制御する必要があります。 Dev API Gesture Recognizers の「タッチの分析からのジェスチャ認識の防止」を参照してください。

UIPageViewControllerのRootViewControllerでは、私のソリューションは次のようになります。

ViewDidLoadの場合:

//EDITED Need to take care of all gestureRecogizers. Got a bug when only setting the delegate for Tap
for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
    gR.delegate = self;
}

オーバーライド:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    //Touch gestures below top bar should not make the page turn.
    //EDITED Check for only Tap here instead.
    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        CGPoint touchPoint = [touch locationInView:self.view];
        if (touchPoint.y > 40) {
            return NO;
        }
        else if (touchPoint.x > 50 && touchPoint.x < 430) {//Let the buttons in the middle of the top bar receive the touch
            return NO;
        }
    }
    return YES;
}

また、RootViewControllerをUIGestureRecognizerDelegateとして設定することを忘れないでください。

(FYI、私は横向きモードのみです。)

編集-上記のコードをSwift 2:

ViewDidLoadの場合:

for gr in self.view.gestureRecognizers! {
    gr.delegate = self
}

Page View ControllerにUIGestureRecognizerDelegateを継承させてから追加します:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if let _ = gestureRecognizer as? UITapGestureRecognizer {
        let touchPoint = touch .locationInView(self.view)
        if (touchPoint.y > 40 ){
            return false
        }else{
            return true
        }
    }
    return true
}
50
jramer

Xcodeテンプレートのself.view.gestureRecognizers = self.pageViewController.gestureRecognizers部分の直後のviewDidLoadテンプレートに追加できる別のソリューションがあります。ジェスチャレコグナイザーの内臓をいじったり、デリゲートを処理したりすることを避けます。ビューからタップジェスチャレコグナイザーを削除し、スワイプレコグナイザーのみを残します。

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

// Find the tap gesture recognizer so we can remove it!
UIGestureRecognizer* tapRecognizer = nil;    
for (UIGestureRecognizer* recognizer in self.pageViewController.gestureRecognizers) {
    if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
        tapRecognizer = recognizer;
        break;
    }
}

if ( tapRecognizer ) {
    [self.view removeGestureRecognizer:tapRecognizer];
    [self.pageViewController.view removeGestureRecognizer:tapRecognizer];
}

ページを切り替えるには、スワイプする必要があります。タップは、ページビューの上にあるコントロールでのみ機能するようになりました(これは私が望んでいたことです)。

55
Pat McG

同じ問題がありました。サンプルとドキュメントは、loadViewまたはviewDidLoadでこれを行います。

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

これにより、UIViewControllersビューのジェスチャレコグナイザーがUIPageViewControllerのGestureRecognizersに置き換えられます。タッチが発生すると、まずpageViewControllersジェスチャレコグナイザーを介して送信されます。一致しない場合、サブビューに送信されます。

その行のコメントを外すだけで、すべてが期待どおりに機能します。

フィリップ

4
Phillip

以下のようにviewRecognizersデリゲートをviewControllerに設定すると、ios6で機能しなくなります

for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
    gR.delegate = self;
}

ios6では、pageViewControllerのgestureRecognizersデリゲートをviewControllerに設定するとクラッシュします

3
noRema

新しいバージョンでは(iOS 8.1以降を対象とするXcode 7.3を使用しています)、これらのソリューションはどれも機能していないようです。

受け入れられた答えはエラーでクラッシュします:

UIScrollViewの組み込みパンジェスチャレコグナイザーには、デリゲートとしてスクロールビューが必要です。

UIPageViewControllerのscrollviewがチェックできない奇妙なジェスチャ認識サブクラスを使用しているように見えるため、現在最高ランクの回答(Pat McGから)も機能しません。したがって、ステートメントif ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] )は実行されません。

各レコグナイザーのcancelsTouchesInViewを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

私は使った

for (UIScrollView *view in _pageViewController.view.subviews) {
    if ([view isKindOfClass:[UIScrollView class]]) {
        view.delaysContentTouches = NO;
    }
}

クリックがUIPageViewController内のボタンに移動できるようにする

2
Adam Johns

私の場合、UIPageControlでのタップを無効にし、画面上の別のボタンでタップが受信されるようにしました。スワイプは引き続き機能します。私は多くの方法を試しましたが、それが最も簡単な作業ソリューションだったと思います。

for (UIPageControl *view in _pageController.view.subviews) {
    if ([view isKindOfClass:[UIPageControl class]]) {
        view.enabled = NO;
    }
}

これは、UIPageControllerサブビューからUIPageControlビューを取得し、ユーザーの操作を無効にします。

1
Leto Baxevanaki

サブクラス化したボタンを使用している場合、touchesBegan、touchesMoved、touchesEndedをオーバーライドし、必要に応じて独自のプログラムページターンを呼び出しますが、superを呼び出してタッチを通知チェーンに渡すことはできません。

0
isaac

ユーザーがさまざまなページビューにジャンプ、カール、スライドするときに、pageviewcontrollersを定期的に作成します。新しいpageviewcontrollerを作成するルーチンでは、上に示した優れたコードの少し単純なバージョンを使用します。

UIPageViewController *npVC = [[UIPageViewController alloc]
                       initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                       navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                       options: options];
...

// Find the pageView tap gesture recognizer so we can remove it!
for (UIGestureRecognizer* recognizer in npVC.gestureRecognizers) {
    if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
        UIGestureRecognizer* tapRecognizer = recognizer;
        [npVC.view removeGestureRecognizer:tapRecognizer];
       break;
    }
}

これで、タップは希望どおりに機能します(左右のタップでページがジャンプし、カールが正常に機能します)。

0
Bill Cheswick

RootViewControllerにサブビュー(新しいIBOutletジェスチャビューにリンク)を作成し、この新しいビューにジェスチャを割り当てます。このビューは、ジェスチャを有効にする画面の部分をカバーしています。

viewDidLoadの変更:

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

に:

self.gesturesView.gestureRecognizers = self.pageViewController.gestureRecognizers;
0
tdf

タップ認識機能を削除するためのSwift 3拡張:

    import UIKit

extension UIPageViewController {
    func removeTapRecognizer() {
        let gestureRecognizers = self.gestureRecognizers

        var tapGesture: UIGestureRecognizer?
        gestureRecognizers.forEach { recognizer in
            if recognizer.isKind(of: UITapGestureRecognizer.self) {
                tapGesture = recognizer
            }
        }
        if let tapGesture = tapGesture {
            self.view.removeGestureRecognizer(tapGesture)
        }
    }
}
0
Roman Barzyczak

これは私にとって最もうまくいった解決策です。境界を超えてページングするときにエラーが発生することを除いて、JRAMERの答えを試しました

PatMCGソリューションでは、すべてのタップ認識機能がキャンセルされたため、十分な柔軟性が得られませんでした。まだラベルが必要ではなく、タップが必要でした

私のUILabelでは、次のように単純にオーバーライドしました。これは、ラベルのみのタップをキャンセルしました

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
 if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
     return NO;
 } else {
     return YES;
 }
}
0
Ryan Heitner

私は実用的な解決策を考え出しました。別のUIGestureRecognizerをUIPageViewControllerに追加し、以下に示すデリゲートメソッドを実装します。どのジェスチャーをロックするか、さらに渡す必要があるかを解決する必要があるすべての瞬間に、このメソッドが呼び出されます。 confictingViewへの参照を忘れずに提供してください。これは、私の場合はUITableViewであり、パンジェスチャも認識します。このビューはUIPageViewController内に配置されたため、パンジェスチャは2回またはランダムに認識されました。このメソッドでは、パンジェスチャがUITableViewとUIPageViewControllerの両方にあるかどうかを確認し、UIPanGestureRecognizerがプライマリであると判断します。

このアプローチは、他のジェスチャレコグナイザーを直接オーバーライドしないため、前述の「NSInvalidArgumentException」について心配する必要はありません。

パターンは実際にはApple :)によって承認されないことに注意してください

 var conflictingView:UIView?
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if otherGestureRecognizer.view === pageViewController?.view {

            if let view = conflictingView {
                var point = otherGestureRecognizer.location(in: self.view)
                if view.frame.contains(point) {
                    print("Touch in conflicting view")
                    return false
                }
            }
            print("Touch outside conficting view")
            return true
        }
        print("Another view passed out")
        return true
    }

UIPageViewController内の子View Controllerのタップに応答するためのシンプルでキャッチオールな方法を探している間に、私はここで終わりました。私のソリューションの中核(Swift 4、Xcode 9)は、RootViewController.Swift(Xcodeの "Page-Based App"テンプレートと同じ構造)で、これと同じくらい簡単になりました:

override func viewDidLoad() {
    super.viewDidLoad()

    ...

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(pageTapped(sender:)))
    self.pageViewController?.view.subviews[0].addGestureRecognizer(tapGesture)
}

...

@objc func pageTapped(sender: UITapGestureRecognizer) {
    print("pageTapped")
}

(また、 this answer を使用して、実際にタップされたページ、つまり現在のページを追跡できるようにしました。)

0
brookinc

また、これを使用することもできます(デリゲートについて言うと、助けてくれてありがとう):

// add UIGestureRecognizerDelegate

NSPredicate *tp = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [UITapGestureRecognizer class]];
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:tp][0];
tgr.delegate = self; // tap delegating

NSPredicate *pp = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [UIPanGestureRecognizer class]];
UIPanGestureRecognizer *pgr = (UIPanGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:pp][0];
pgr.delegate = self; // pan delegating

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 
{
    CGPoint touchPoint = [touch locationInView:self.view];

    if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 915 ) {
        return NO; // if y > 915 px in portrait mode
    }

    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 680 ) {
        return NO; // if y > 680 px in landscape mode
    }

    return YES;
}

私のために完璧に働く:)

0
iTux