web-dev-qa-db-ja.com

UIPageViewControllerでのアサーションの失敗

UIPageViewControllerでアサーションエラーが発生しました。

Assertion failure in -[UIPageViewController _flushViewController:animated:], /BuildRoot/Library/Caches/com.Apple.xbs/Sources/UIKit/UIKit-3512.60.12/UIPageViewController.m
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'Don't know about flushed view <UIView: 0x15a5bff30; frame = (0 0; 768 903); autoresize = W+H; layer = <CALayer: 0x15a5bfc30>>'
*** First throw call stack:
(0x181ebedb0 0x181523f80 0x181ebec80 0x182844154 0x1877a1c40 0x1877a1da8 0x18784e9c4 0x18784ebfc 0x187852318 0x18784dd98 0x1870101e4 0x1849a2994 0x18499d5d0 0x1870270a4 0x10028b620 0x100348b78 0x100379f54 0x100168878 0x18733d568 0x1870330b4 0x1870f1a00 0x18733e71c 0x1870f832c 0x18703536c 0x18700f7ac 0x18700ed40 0x18700eba8 0x1873283b4 0x18700d5e8 0x18784ebd4 0x187852318 0x18784df3c 0x1871db550 0x1871daf6c 0x101c9b768 0x1849f0234 0x1849f00e8 0x182135e54 0x181e5d030 0x181e757d4 0x181e74f0c 0x181e72c64 0x181d9cc50 0x183684088 0x18707e088 0x10033b200 0x18193a8b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

このエラーが発生している理由がわかりません。それを引き起こしているものやそれをデバッグする方法についての手がかりはありますか?

14
GyeongWon Do

このアサーションに遭遇する直接的な方法は、スクロール遷移スタイルで定義されたUIPageControllerの循環ソースを使用することです。

ソースに2つのページが含まれている場合、それぞれが前のページと次のページになります。 2ページを含むUIPageControllerをスワイプしてから、3ページでソースを設定しようとすると、2ページの場合にUIPageControllerDataSourcebefore/afterメソッドが循環遷移を許可すると仮定して上記のアサーションを取得します。

スクロールトランジションでUIPageControllerを使用するクラッシュフリーの主なルール:

1)dataSourceメソッドを呼び出す前にsetViewControllersを設定します

2)アニメーションなしでsetViewControllersメソッドを使用します(animated: false

3)シングルページモードの場合はdataSourceをnilに設定します

4)2ページモードのサイクルを許可しない

これらすべての推奨事項を合わせると、UIPageControllerは完全に安定します。

import UIKit

/// Convenient subclass of UIPageViewController
@objc class AMPageViewController: UIPageViewController {

    /// Turn on/off PageControl at the bottom
    @objc var showPageControl: Bool = true

    /// Array of all viewControllers
    @objc var source: [UIViewController]? {

        didSet {
            let count = source?.count ?? 0
            if count > 0 {
                dataSource = count > 1 ? self : nil
            }
            else {
                dataSource = nil
                delegate = nil
            }
        }
    }

    /// Index of the current viewController from source
    @objc var pageIndex: Int {

        get {
            var currentPageIndex: Int = 0
            if let vc = viewControllers?.first, let source = source, let pageIndex = source.index(of: vc) {
                currentPageIndex = pageIndex
            }

            return currentPageIndex
        }

        set {
            guard newValue >= 0, let source = source, newValue < source.count else { return }

            let vc = source[newValue]
            let direction: UIPageViewControllerNavigationDirection = newValue < pageIndex ? .reverse : .forward

            setViewController(vc, direction: direction)
        }
    }

    override weak var delegate: UIPageViewControllerDelegate? {

        get { return super.delegate }

        set {
            if source?.count ?? 0 > 0 {
                super.delegate = newValue
            }
            else {
                super.delegate = nil
            }
        }
    }

    /// Initializer in scroll-mode with interPageSpacing
    @objc init(navigationOrientation: UIPageViewControllerNavigationOrientation = .horizontal, interPageSpacing: Int = 0) {

        let options = (interPageSpacing > 0) ? [UIPageViewControllerOptionInterPageSpacingKey : 5] : nil

         super.init(transitionStyle: .scroll, navigationOrientation: navigationOrientation, options: options)
     }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// Set viewcontroller by index from source
    @objc func setPageIndex(_ index: Int, completion: ((Bool) -> Void)? = nil) {

        guard index > 0, let source = source, index < source.count else { return }

        let vc = source[index]
        let direction: UIPageViewControllerNavigationDirection = index < pageIndex ? .reverse : .forward

        setViewController(vc, direction: direction, completion: completion)
    }


    private func setViewController(_ viewController: UIViewController, direction: UIPageViewControllerNavigationDirection = .forward, completion: ((Bool) -> Void)? = nil) {

        super.setViewControllers([viewController], direction: direction, animated: false, completion: completion)
    }
}

extension FFPageViewController: UIPageViewControllerDataSource {

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {

        guard let source = source, let index = source.index(of: viewController) else { return nil }

        let count = source.count

        if count == 2, index == 0 {
            return nil
        }

        let prevIndex = (index - 1) < 0 ? count - 1 : index - 1

        let pageContentViewController: UIViewController = source[prevIndex]

        return pageContentViewController
    }


    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

        guard let source = source, let index = source.index(of: viewController) else { return nil }

        let count = source.count

        if count == 2, index == 1 {
            return nil
        }

        let nextIndex = (index + 1) >= count ? 0 : index + 1

        let pageContentViewController = source[nextIndex]

        return pageContentViewController
    }


    func presentationCount(for pageViewController: UIPageViewController) -> Int {

        return showPageControl ? (source?.count ?? 0) : 0
    }


    func presentationIndex(for pageViewController: UIPageViewController) -> Int {

        guard showPageControl else { return 0 }

        return pageIndex
    }
}

GitHubプロジェクト にある全体的な実装と使用例。

3
malex

これは、子コントローラーにテキストフィールドがあり、次のコントローラーにスクロールするときにキーボードを閉じなかったときにも発生しました。この場合、コントローラーをプログラムで変更するアクションでendEditingを追加するか、pageViewControllerのscrollViewDidScrollデリゲートメソッドをスクロールします。

1
Atan

これは、UIPageViewControllerTransitionStylescrollではなくpageCurlに設定されている場合に発生します。

ビューコントローラを動的に作成し、UIPageViewControllerに設定していますか?その場合、UIKitのバグのため、最初の呼び出しがアニメーションを完了した後に、setViewControllersへの2番目の呼び出しが呼び出されるようにする必要があります。ディスパッチの遅延は迅速で汚い修正ですが、良い習慣ではありません

詳細はこちら。

https://forums.developer.Apple.com/thread/6554

1
Mugunth

私の場合、問題は、pageViewControllerデリゲートメソッドで取得されたパラメーターとしてdidFinishAnimatingではなく、現在のViewControllerのメンバーとしてself.pageViewControllerを使用することでした。

1
Armin

コードで実行している場合は、pageViewController.setViewControllers関数呼び出しをDispatchQueue.main.asyncブロック内に移動します。

なぜそれが機能するのかわかりませんが、私にとってはうまくいきました。 参考までに

0

UIPageViewControllerトランジションの場合、その中のViewController(例:UITableViewController)トランジションによりクラッシュが発生します。

私の場合(クラッシュ):

ステップ1

self.pageViewController.setViewControllers([self.tableViewController2], direction: .forward, animated: true, completion: nil)

ステップ2

UIPageViewController遷移中にtableViewをスクロールします。

私の解決策

(ターゲットViewControllerと現在のViewControllerの両方のスクロールを無効にします)

self.tableViewController1.tableView.isScrollEnabled = false
self.tableViewController2.tableView.isScrollEnabled = false
self.pageViewController.setViewControllers([self.tableViewController2], direction: .forward, animated: true, completion: { _ in
    self.tableViewController1.tableView.isScrollEnabled = true
    self.tableViewController2.tableView.isScrollEnabled = true
})
0
Codus