UIPageViewController代理崩溃:NSInternalInconsistencyException异常求助
这个崩溃我之前在使用UIPageViewController的scroll过渡风格时也碰到过,核心原因是系统内部的断言检查失败——scroll风格的UIPageViewController需要同时持有当前页面、前一个页面(如果存在)和后一个页面(如果存在)的控制器实例,当它无法获取到足够的页面控制器时,就会触发NSInternalInconsistencyException,报错[views count] == 3。
可能的问题原因
UIPageViewController过渡风格为Scroll,且DataSource方法未正确实现
当使用UIPageViewControllerTransitionStyle.scroll时,系统会预加载当前页面的前后页面。如果你的pageDataSource没有正确实现pageViewController(_:viewControllerBefore:)和pageViewController(_:viewControllerAfter:)这两个方法,导致系统无法获取到必要的前后页面控制器,就会触发断言。在过渡未完全完成时调用
setViewControllers
你在didFinishAnimating回调里直接调用rotateFirstPage(),但如果transitionCompleted为false(比如用户中途取消了滑动过渡),此时页面控制器的内部状态还不稳定,强行设置新的视图控制器会导致状态混乱。页面数组长度不足且未做边界处理
如果你的pages数组只有1个元素,scroll风格的UIPageViewController在尝试预加载前后页面时会获取到nil,此时内部维护的视图控制器数量不满足断言要求。
解决方案
1. 确保DataSource方法正确实现
检查你的pageDataSource是否正确实现了前后页面的获取逻辑,示例代码如下:
// 实现获取前一个页面的方法 func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let currentIndex = pageDataSource.pages.firstIndex(of: viewController) else { return nil } // 第一个页面没有前一个页面,返回nil return currentIndex > 0 ? pageDataSource.pages[currentIndex - 1] : nil } // 实现获取后一个页面的方法 func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let currentIndex = pageDataSource.pages.firstIndex(of: viewController) else { return nil } // 最后一个页面没有后一个页面,返回nil return currentIndex < pageDataSource.pages.count - 1 ? pageDataSource.pages[currentIndex + 1] : nil }
2. 仅在过渡完成后执行页面切换
修改didFinishAnimating回调,只有当transitionCompleted为true时才执行rotateFirstPage()和更新pageControl:
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { // 只有过渡真正完成时才执行自动回滚逻辑 if firstAutoRotation && completed { Timer?.invalidate() rotateFirstPage() } // 仅在过渡完成时更新pageControl,避免状态错误 if completed { pageControl.currentPage = pageDataSource.presentationIndex(for: pageViewController) } }
3. 处理页面数组长度不足的边界情况
如果你的pages数组可能只有1个元素,可以考虑两种处理方式:
- 切换UIPageViewController的过渡风格为
pageCurl(该风格不需要预加载前后页面); - 在调用
rotateFirstPage()前先判断数组长度,避免不必要的页面切换:private func rotateFirstPage() { guard let pageViewController = pageViewController, pageDataSource.pages.count > 1 else { // 如果只有一个页面,无需执行切换逻辑 return } if let firstPage = pageDataSource.pages.first { pageViewController.setViewControllers([firstPage], direction: .forward, animated: true, completion: nil) } }
内容的提问来源于stack exchange,提问作者Anpas

