web-dev-qa-db-ja.com

UIPageViewControllerはスクロール遷移スタイルで間違ったページに移動します

UIPageViewControllerはiOS 5で正常に機能していましたが、iOS 6が登場したとき、ページカールスタイルの代わりに新しいスクロール遷移スタイル(UIPageViewControllerTransitionStyleScroll)を使用したかったのです。これにより、UIPageViewControllerが破損しました。

setViewControllers:direction:animated:completion:を呼び出した直後を除き、正常に動作します。その後、ユーザーが次に1ページずつ手動でスクロールすると、間違ったページが表示されます。ここで何が問題なのですか?

45
matt

このバグの私の回避策は、同じviewcontrollerを設定していたがアニメーションなしで終了したときにブロックを作成することでした

__weak YourSelfClass *blocksafeSelf = self;     
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished){
            if(finished)
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [blocksafeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];// bug fix for uipageview controller
                });
            }
        }];
78

これは、実際にはUIPageViewControllerのバグです。スクロールスタイル(UIPageViewControllerTransitionStyleScroll)でのみ発生し、setViewControllers:direction:animated:completion:with animated:YESを呼び出した後にのみ発生します。したがって、2つの回避策があります。

  1. UIPageViewControllerTransitionStyleScrollを使用しないでください。

  2. または、setViewControllers:direction:animated:completion:を呼び出す場合は、animated:NOのみを使用してください。

バグを明確に見るには、setViewControllers:direction:animated:completion:を呼び出してから、インターフェースで(ユーザーとして)、左(戻る)に手動で前のページに移動します。間違ったページに戻ります。前のページではなく、setViewControllers:direction:animated:completion:が呼び出されたときに表示されていたページです。

バグの理由は、スクロールスタイルを使用する場合、UIPageViewControllerが何らかの内部キャッシュを行うためと思われます。したがって、setViewControllers:direction:animated:completion:の呼び出し後、内部キャッシュのクリアに失敗します。前のページが何であるかを知っていると考えます。したがって、ユーザーが前のページに左に移動すると、UIPageViewController dataSourceメソッドpageViewController:viewControllerBeforeViewController:の呼び出しに失敗するか、間違った現在のView Controllerで呼び出します。

バグの見方を明確に示すムービーを投稿しました。

http://www.apeth.com/PageViewControllerBug.mov

[〜#〜] edit [〜#〜]このバグはおそらくfixediOS 8。

[〜#〜] edit [〜#〜]このバグの別の興味深い回避策については、この回答を参照してください: https:// stackoverflow .com/a/21624169/341994

58
matt

ここ は、私がまとめた「大まかな」要点です。これには、アルツハイマーの影響を受けるUIPageViewControllerの代替が含まれています(つまり、Apple実装)の内部キャッシュがありません)。

このクラスは完全ではありませんが、私の状況では機能します(つまり、水平スクロール)。

3
Paul de Lange

IOS 12の時点で、元の質問で説明された問題はほぼ修正されたようです。私がこの質問に出会ったのは、特定のセットアップでそれを経験したためです。

この問題が発生したセットアップは次のとおりです。1)アプリがディープリンク経由で開かれた2)アプリが特定のタブに切り替えてプッシュで特定のアイテムを開く必要があるリンクに基づいて3)ターゲットが発生した場合にのみ問題が発生したタブが以前にユーザーによって選択されていなかったため(UIPageViewControllerがそのタブをアニメーション化することになっているため)、setViewControllers:direction:animated:completion: 持っていました animated = true 4)プッシュがUIPageViewControllerを含むView Controllerに戻った後、UIPageViewControllerは大きな混乱であることが判明しました。

問題の根本は、setViewControllers:direction:animated:completion:が呼び出されたため、UIPageViewControllerが何か(アニメーション、キャッシュ、または他の何か)を終了する機会がありませんでした。

UIでプログラムによるナビゲーションを遅らせることで、UIPageViewControllerに空き時間を与えるだけです。

 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { ... } 

私のために問題を修正しました。また、リンクされたアイテムのプログラムによるオープニングが視覚的にユーザーフレンドリになりました。

これが同様の状況で誰かを助けることを願っています。

1
Vitalii

このバグはまだiOS9に存在します。上記のGeorge Tsifrikasと同じ回避策を使用していますが、Swiftバージョン:

    pageViewController.setViewControllers([page], direction: direction, animated: true) { done in
        if done {
            dispatch_async(dispatch_get_main_queue()) {
                self.pageViewController.setViewControllers([page], direction: direction, animated: false, completion: {done in })
            }
        }
    }
0
phatmann

PageviewVCはスワイプするとmulti childVCを呼び出すためです。ただし、表示される最後のページだけが必要です。

私の場合、pageViewを変更すると、セグメント化されたコントロールのインデックスを変更する必要があります。

これが誰かを助けることを願っています:)

extension ViewController: UIPageViewControllerDelegate {

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        guard let pageView = pageViewController.viewControllers?.first as? ChildViewController else { return }
        segmentedControl.set(pageView.index)
    }
}
0
logan.Nguyen

Swiftの別の簡単な回避策:UIPageViewControllerのデータソースをリセットするだけです。これは明らかにキャッシュをクリアし、バグを回避します。次のスワイプを中断せずにページに直接移動する方法を次に示します。以下では、m_pagesはView Controllerの配列です。 currPage(現在のページのインデックス)を見つける方法を以下に示します。

func goToPage(_ index: Int, animated: Bool)
{
    if m_pages.count > 0 && index >= 0 && index < m_pages.count && index != currPage
    {
        var dir: UIPageViewController.NavigationDirection
        if index < currPage
        {
            dir = UIPageViewController.NavigationDirection.reverse
        }
        else
        {
            dir = UIPageViewController.NavigationDirection.forward
        }

        m_pageViewController.setViewControllers([m_pages[index]], direction: dir, animated: animated, completion: nil)
        delegate?.tabDisplayed(sender: self, index: index)
        m_pageViewController.dataSource = self;
    }
}

現在のページを見つける方法:

var currPage: Int
{
    get
    {
        if let currController = m_pageViewController.viewControllers?[0]
        {
            return m_pages.index(of: currController as! AtomViewController) ?? 0
        }

        return 0
    }
}
0
Oscar