8

UIPageViewControllerに非常に興味深い問題があります。

私のプロジェクトは、サンプルのページベースのアプリケーションテンプレートと非常によく似ています。時々(ただし、ある程度再現可能)、特定のパンジェスチャがに呼び出され-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewControllerます。

次のページのビューコントローラを返しますが、ページめくりアニメーションが実行されず、デリゲートメソッドが呼び出されません。

これがのコードですviewControllerAfterViewController

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    PageDisplayViewController *vc = (PageDisplayViewController *)viewController;
    NSUInteger index = [self.pageFetchController.fetchedObjects indexOfObject:vc.page];
    if(index == (self.pageFetchController.fetchedObjects.count - 1)) return nil;
    return [self getViewControllerForIndex:(++index)];
}

これがgetViewControllerForIndex:

-(PageDisplayViewController *)getViewControllerForIndex:(NSUInteger)index
{
    PageDisplayViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:@"PageDisplayController"];
    newVC.page = [self.pageFetchController.fetchedObjects objectAtIndex:(index)];
    newVC.view.frame = CGRectMake(0, 0, 1024, 604);
    NSLog(@"%i", index);
    if(index == 0)
    {
        //We're moving to the first, animate the back button to be hidden.
        [UIView animateWithDuration:0.5 animations:^
        {
            self.backButton.alpha = 0.f;
        } completion:^(BOOL finished){
            self.backButton.hidden = YES;
        }];
    }
    else if(index == (self.pageFetchController.fetchedObjects.count - 1))
    {
        [UIView animateWithDuration:0.5 animations:^{
            self.nextButton.alpha = 0.f;
        } completion:^(BOOL finished){
            self.nextButton.hidden = YES;
        }];
    }
    else
    {
        BOOL eitherIsHidden = self.nextButton.hidden || self.backButton.hidden;
        if(eitherIsHidden)
        {
            [UIView animateWithDuration:0.5 animations:^{
                if(self.nextButton.hidden)
                {
                    self.nextButton.hidden = NO;
                    self.nextButton.alpha = 1.f;
                }
                if(self.backButton.hidden)
                {
                    self.backButton.hidden = NO;
                    self.backButton.alpha = 1.f;
                }
            }];
        }
    }
    return newVC;
}

基本的には、View Controllerを作成し、そのデータオブジェクトを設定してから、インデックスに応じて[次へ]/[戻る]ボタンをフェードアウトします。

デリゲート方式

-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
    PageDisplayViewController *vc = [previousViewControllers lastObject];
    NSUInteger index = [self.pageFetchController.fetchedObjects indexOfObject:vc.page];

    if (!completed)
    {
        [self.pagePreviewView setCurrentIndex:index];
        NSLog(@"Animation Did not complete, reverting pagepreview");
    }
    else
    {
        PageDisplayViewController *curr = [pageViewController.viewControllers lastObject];
        NSUInteger i = [self.pageFetchController.fetchedObjects indexOfObject:curr.page];
        [self.pagePreviewView setCurrentIndex:i];
        NSLog(@"Animation compeleted, updating pagepreview. Index: %u", i);
    }
}

この問題に気付いたのは、ランダムに戻るボタンが画面に再表示されるためです。そこにいくつかのNSLog()ステートメントを入れた後、dataSourceメソッドがインデックス1に対して呼び出されることに気付きましたが、アニメーションが再生されたり、デリゲートが呼び出されたりすることはありません。さらに恐ろしいのは、次のページをパンしようとすると、インデックス1が再度呼び出されることです。

これはUIPageViewControllerのバグである可能性があります。

4

4 に答える 4

8

私は最初の回答の実装でまだ不思議なクラッシュを受け取っていたので、ページビューコントローラー(PVC)の基本的な動作に関する個人的な仮定にあまり依存しない「十分に良い」ソリューションを探し続けました。これが私が思いついたものです。

私の以前のアプローチは一種の煩わしいものであり、許容できる解決策というよりは回避策でした。PVCと戦って、私が思っていたことを強制するのではなく、次の事実を受け入れるほうがよいようです。

  • pageViewController:viewControllerBeforeViewController:およびメソッドは、pageViewController:viewControllerAfterViewController:UIKitによって任意の回数呼び出すことができます。
  • これらのいずれかがページングアニメーションに対応するという保証も、その後に次の呼び出しが続くという保証もありません。pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

つまり、before / afterメソッドを「animation-begin」として使用することはできません(ただし、これはdidFinishAnimating「animation-end」イベントとして機能します)。では、アニメーションが実際に始まったことをどうやって知るのでしょうか?

ニーズに応じて、次のイベントに関心がある場合があります。

  1. ユーザーはページをいじり始めます。これを示す良い指標は、コールバックの前後、より正確には最初のコールバックです。

  2. stateページめくりジェスチャの最初の視覚的フィードバック: PVCのタップおよびパンジェスチャレコグナイザーのプロパティでKVOを使用できます。パンのUIGestureRecognizerStateBegan値が観察されると、視覚的なフィードバックが続くことを確信できます。

  3. ユーザーはタッチを離してページのドラッグを終了します。ここでも、KVOです。パンまたはタッピングのいずれかで値が報告される場合UIGestureRecognizerStateRecognized、それはPVCが実際にページをめくるときであるため、これは「アニメーション開始」として使用できます。

  4. UIKitはページングアニメーションを開始します。これに対する直接のフィードバックを取得する方法がわかりません。

  5. UIKitは、ページングアニメーションを終了します。ケーキの一部です。聞いてくださいpageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

KVOの場合は、次のようにPVCのジェスチャレコグナイザーを取得します。

@interface MyClass () <UIGestureRecognizerDelegate>
{
    UIPanGestureRecognizer* pvcPanGestureRecognizer;
    UITapGestureRecognizer* pvcTapGestureRecognizer;
}
...
for ( UIGestureRecognizer* recognizer in pageViewController.gestureRecognizers )
{
    if ( [recognizer isKindOfClass:[UIPanGestureRecognizer class]] )
    {
        pvcPanGestureRecognizer = (UIPanGestureRecognizer*)recognizer;
    }
    else if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] )
    {
        pvcTapGestureRecognizer = (UITapGestureRecognizer*)recognizer;
    }
}

state次に、クラスをプロパティのオブザーバーとして登録します。

[pvcPanGestureRecognizer addObserver:self
                          forKeyPath:@"state"
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
[pvcTapGestureRecognizer addObserver:self
                          forKeyPath:@"state"
                             options:NSKeyValueObservingOptionNew
                             context:NULL];

そして、通常のコールバックを実装します。

- (void)observeValueForKeyPath:(NSString *)keyPath
        ofObject:(id)object
        change:(NSDictionary *)change
        context:(void *)context
{
    if ( [keyPath isEqualToString:@"state"] && (object == pvcPanGestureRecognizer || object == pvcTapGestureRecognizer) )
    {
        UIGestureRecognizerState state = [[change objectForKey:NSKeyValueChangeNewKey] intValue];
        switch (state)
        {
            case UIGestureRecognizerStateBegan:
            // trigger for visual feedback
            break;

            case UIGestureRecognizerStateRecognized:
            // trigger for animation-begin
            break;

            // ...
        }
    }
}

完了したら、これらの通知の購読を解除することを忘れないでください。そうしないと、アプリでリークや奇妙なクラッシュが発生する可能性があります。

[pvcPanGestureRecognizer removeObserver:self
                             forKeyPath:@"state"];
[pvcTapGestureRecognizer removeObserver:self
                             forKeyPath:@"state"];

それはすべての人々です!

于 2012-11-22T18:58:12.893 に答える
4

私はあなたの解決策を試しました、そしてそれはほとんどうまくいきました、しかしそれでもいくつかの問題があります。最良の解決策は、メソッドを追加することでした

 - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers 

これはiOS6から利用可能であり、それに必要です。実装しないと、これらのジェスチャで問題が発生する可能性があります。それを実装することは、問題の大部分を解決するのに役立ちました。

于 2012-12-13T10:27:21.233 に答える
4

そもそも私の他の答えを見てください。これには重大な欠陥がありますが、それでも誰かを助けるかもしれないので、ここに残しておきます。


まず、免責事項:次の解決策はHACKです。それは私がテストした環境で動作しますが、それがあなたの環境で動作するという保証も、次のアップデートで壊れないという保証もありません。したがって、慎重に進めてください。

TL; DR:を取得し、UIPanGestureRecognizerそのUIPageViewControllerデリゲートコールをハイジャックしますが、元のターゲットに転送し続けます。

長いバージョン:

この問題に関する私の調査結果:UIPageViewControlleriOS 6に同梱されているものは、iOS 5のものとは動作が異なり、pageViewController:viewControllerBeforeViewController:どのような意味でもページめくりが行われていなくても、データソースを呼び出す可能性があります(読み取り:タップなし、スワイプ、または、有効な方向一致パンが認識されています)。もちろん、これは、前/後の呼び出しが「アニメーションの開始」トリガーと同等であり、常にpageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:デリゲートへの呼び出しが続くという以前の仮定を破ります。(結局、これは大胆な仮定ですが、私はそれだけではなかったと思います。)

UIPanGestureRecognizerページビューコントローラーのデフォルトが、最終的にコントローラーの方向と一致しないパンジェスチャを認識し始めたときに、データソースへの余分な呼び出しが発生する可能性があることがわかりました(たとえば、水平ページングPVCでの垂直パン)。興味深いことに、私の環境では、ヒットしたのは常に「前」のメソッドであり、「後」ではありませんでした。他の人はジェスチャレコグナイザーのデリゲートに干渉することを提案しましたが、それはそこで説明されているようには機能しなかったので、私は実験を続けました。

最後に、回避策を見つけました。まず、ページビューコントローラのパンジェスチャレコグナイザを取得します。

@interface MyClass () <UIGestureRecognizerDelegate>
{
    UIPanGestureRecognizer* pvcPanGestureRecognizer;
    id<UIGestureRecognizerDelegate> pvcPanGestureRecognizerDelegate;
}
...
for ( UIGestureRecognizer* recognizer in pageViewController.gestureRecognizers )
{
    if ( [recognizer isKindOfClass:[UIPanGestureRecognizer class]] )
    {
        pvcPanGestureRecognizer = (UIPanGestureRecognizer*)recognizer;
        pvcPanGestureRecognizerDelegate = pvcPanGestureRecognizer.delegate;
        pvcPanGestureRecognizer.delegate = self;
        break;
    }
}

次にUIGestureRecognizerDelegate、クラスにプロトコルを実装します。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ( gestureRecognizer == pvcPanGestureRecognizer &&
        [pvcPanGestureRecognizerDelegate respondsToSelector:@selector(gestureRecognizer:shouldReceiveTouch:)] )
    {
        return [pvcPanGestureRecognizerDelegate gestureRecognizer:gestureRecognizer
                                               shouldReceiveTouch:touch];
    }
    return YES;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
    shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if ( gestureRecognizer == pvcPanGestureRecognizer &&
        [pvcPanGestureRecognizerDelegate respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)] )
    {
        return [pvcPanGestureRecognizerDelegate gestureRecognizer:gestureRecognizer
               shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
    }
    return NO;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if ( gestureRecognizer == pvcPanGestureRecognizer &&
        [pvcPanGestureRecognizerDelegate respondsToSelector:@selector(gestureRecognizerShouldBegin:)] )
    {
        return [pvcPanGestureRecognizerDelegate gestureRecognizerShouldBegin:gestureRecognizer];
    }
    return YES;
}

どうやら、メソッドは賢明なことは何もしません。呼び出しを元のデリゲートに転送するだけです(実際に実装されていることを確認してください)。それでも、この転送は、PVCが動作し、必要がないときにデータソースを呼び出さないために十分であるように思われます。

この回避策は、iOS 6を実行しているデバイスでの問題を修正しました。iOS6SDKでコンパイルされたが、iOS 5の展開ターゲットでコンパイルされたコードはすでに5.xデバイスで問題なく実行されていたため、修正は必要ありません。私のテストでも害はありません。

誰かがこれが役に立つと思うことを願っています。

于 2012-11-07T10:55:25.213 に答える
0

これを試して...

    - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
 {   

        for (UIGestureRecognizer *gr in pageViewController.gestureRecognizers) {
                if([gr isKindOfClass:[UIPanGestureRecognizer class]])
                {
                    UIPanGestureRecognizer *pgr = (UIPanGestureRecognizer*)gr;
                    CGPoint velocity = [pgr velocityInView:pageViewController.view];
                    BOOL verticalSwipe = fabs(velocity.y) > fabs(velocity.x);
                    if(verticalSwipe)
                        return nil;
                }
            }
    ....
}
于 2017-07-18T12:19:40.557 に答える