UIPageViewController子类滑动至下一页(after)时黑屏问题求助
Hey Kiren, let's figure out why you're hitting that black screen when swiping to the next page. The core issue here is you're updating the currentIndex directly in your UIPageViewController data source methods—that's not the right place to handle state changes for this component.
Here's the breakdown: UIPageViewController calls its data source methods (like viewControllerAfter) to preload adjacent pages before a swipe finishes. When you increment currentIndex in that method, you're altering your coordinator's state prematurely—even if the user cancels the swipe, your index is already out of sync. This leads to incorrect view controllers being returned (or nil when it shouldn't be), which causes the black screen.
Let's Fix This Step by Step
- Update your coordinator protocol to add a way to find the index of a given view controller (we'll need this to sync the index correctly after swipes).
- Rework the coordinator to remove index updates from the method that returns view controllers, and add the index lookup logic.
- Move index updates to the delegate callback—this ensures we only update the index when a swipe fully completes, not during preloading.
Modified Code Snippets
1. Updated Coordinator.swift
import UIKit enum PageViewDirection { case before case after } typealias Index = Int protocol PageViewCoordinator: class { var currentIndex: Index { get set } var viewControllers: [UIViewController] { get set } func viewController(for direction: PageViewDirection) -> UIViewController? func index(of viewController: UIViewController) -> Index? // New helper method }
2. Updated OnboardingViewCoordinator.swift
import UIKit final class OnboardingViewCoordinator: PageViewCoordinator { var currentIndex: Index var viewControllers: [UIViewController] init() { self.viewControllers = [ OnboardingTaglineViewController(nibName: nil, bundle: nil), OnboardingSourcesViewController(nibName: nil, bundle: nil) ] self.currentIndex = 0 } // Now only returns the correct VC without modifying state func viewController(for direction: PageViewDirection) -> UIViewController? { switch direction { case .before: guard currentIndex > 0 else { return nil } return viewControllers[currentIndex - 1] case .after: guard currentIndex < viewControllers.count - 1 else { return nil } return viewControllers[currentIndex + 1] } } // Finds the index of a VC using reference equality (avoids mismatches) func index(of viewController: UIViewController) -> Index? { return viewControllers.firstIndex { $0 === viewController } } }
3. Updated WarmPushPageViewController.swift
import UIKit class WarmPushPageViewController: UIPageViewController { var coordinator: PageViewCoordinator? = nil convenience init(coordinator: PageViewCoordinator) { self.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) self.setViewControllers([coordinator.viewControllers[coordinator.currentIndex]], direction: .forward, animated: true, completion: nil) self.coordinator = coordinator } override func viewDidLoad() { super.viewDidLoad() self.delegate = self self.dataSource = self } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } extension WarmPushPageViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let coordinator = self.coordinator else { return nil } return coordinator.viewController(for: .before) } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let coordinator = self.coordinator else { return nil } return coordinator.viewController(for: .after) } } // Add this delegate extension to handle index updates ONLY after a full swipe extension WarmPushPageViewController: UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed, let coordinator = coordinator, let currentVC = pageViewController.viewControllers?.first, let newIndex = coordinator.index(of: currentVC) else { return } // Only update the index if the transition fully completed coordinator.currentIndex = newIndex } }
Why This Works
- State updates only happen on completed swipes: The
didFinishAnimatingdelegate method ensures we only adjustcurrentIndexwhen the user has fully moved to a new page, not when the page is just being preloaded. - DataSource methods stay stateless: Now,
viewControllerBeforeandviewControllerAftersimply return the correct adjacent page based on the current index, no state changes included. This eliminates index desync issues. - Accurate index lookup: Using reference equality (
$0 === viewController) guarantees we find the exact index of the displayed view controller, even if you have multiple instances of the same VC type.
Give this a test run—this should fix the black screen issue when swiping forward. Let me know if you hit any other bumps!
内容的提问来源于stack exchange,提问作者srinitude

