I have an Assertion Failure in UIPageViewController.
Assertion failure in -[UIPageViewController _flushViewController:animated:], /BuildRoot/Library/Caches/c
The direct way to run into this assert is to use cycled source for UIPageController
defined with scroll transition style.
When the source contains two pages each one is the previous and the next for another one. If you swipe UIPageController
containing two pages and then try to set source with 3 pages you will get the assertion mentioned above with guarantee assuming that UIPageControllerDataSource
before/after methods allow cycled transition in case of 2 pages.
The main rules of crash-free using UIPageController
with scroll transition:
1) set dataSource
before calling setViewControllers
method
2) use setViewControllers
method without animation (animated: false
)
3) set dataSource
to nil for single page mode
4) don't allow cycles for 2-page mode
All these recommendations together make UIPageController
absolutely stable.
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
}
}
The overall implementation and usage examples one can find at GitHub project.
For me, the issue was using self.pageViewController
as a member of the current view controller instead of pageViewController
as parameter obtained in the didFinishAnimating
delegate method.
Move your pageViewController.setViewControllers
function call inside DispatchQueue.main.async
block if you are doing it in code.
I don't know why it works but it worked for me. For reference.
When UIPageViewController
transition, ViewController inside it(ex: UITableViewController) transition will cause crash.
In my case (crash):
step1
self.pageViewController.setViewControllers([self.tableViewController2], direction: .forward, animated: true, completion: nil)
step2
Scroll the tableView while UIPageViewController
transition.
My Solution
(disable scroll both target view controller and current view controller)
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
})
This happened to me too when I had textfields in child controller and didn't dismiss keyboard on scroll to next controller. If this is case just add endEditing in action where you programmatically change your controller or if you are scrolling on scrollViewDidScroll delegate method of pageViewController
This happens when your UIPageViewControllerTransitionStyle
is set to scroll
instead of pageCurl
.
Are you dynamically creating View Controllers and setting them on UIPageViewController? In that case, you must ensure that the second call to setViewControllers is called after the first one completes animation because of a bug in UIKit. A delayed dispatch is a quick and dirty fix, though it is not a good practice
More details here.
https://forums.developer.apple.com/thread/6554