iOS开发:UIPageViewController的两个子视图控制器间数据传递问题
Hey there! Let's work through this data passing problem between your two child view controllers in a UIPageViewController—it's a common scenario once you dig into page-based flows, and we've got a few solid approaches tailored to your setup.
1. Shared Data Source (Great for Bidirectional/Shared State)
If both child VCs need to access or modify the same piece of data, a shared data manager is a clean solution. You can use a singleton (for small apps) or inject an instance through dependency injection (better for testability). Add property observers or notification support to trigger updates when data changes.
// Shared data manager class class PageDataManager { static let shared = PageDataManager() // Singleton instance private init() {} // Prevent external initialization var userInput: String = "" { didSet { // Notify other VCs when data updates NotificationCenter.default.post( name: NSNotification.Name("PageDataUpdated"), object: nil, userInfo: ["newData": userInput] ) } } } // First child VC: Update the shared data class FirstChildVC: UIViewController { @IBAction func saveButtonTapped(_ sender: UIButton) { PageDataManager.shared.userInput = "Data from First VC" } } // Second child VC: Listen for data updates class SecondChildVC: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Subscribe to data change notifications NotificationCenter.default.addObserver( self, selector: #selector(handleDataUpdate(_:)), name: NSNotification.Name("PageDataUpdated"), object: nil ) } @objc private func handleDataUpdate(_ notification: Notification) { if let newData = notification.userInfo?["newData"] as? String { // Update your UI or process the data here print("Received data: \(newData)") } } deinit { // Clean up observer to avoid memory leaks NotificationCenter.default.removeObserver(self) } }
2. Delegate Pattern (Clean Unidirectional Flow)
For explicit one-way data passing (e.g., from the first VC to the second), use a delegate protocol. The parent UIPageViewController acts as the middleman to pass data between children.
// Define a delegate protocol protocol FirstChildVCDelegate: AnyObject { func firstChildDidUpdateData(_ data: String) } // First child VC: Hold a weak reference to the delegate class FirstChildVC: UIViewController { weak var delegate: FirstChildVCDelegate? @IBAction func sendDataButtonTapped(_ sender: UIButton) { delegate?.firstChildDidUpdateData("Data from First VC") } } // Parent UIPageViewController: Implement the delegate and pass data to the second VC class ParentPageVC: UIPageViewController, UIPageViewControllerDataSource, FirstChildVCDelegate { private var secondChildVC: SecondChildVC? override func viewDidLoad() { super.viewDidLoad() dataSource = self // Initialize child VCs let firstVC = FirstChildVC() firstVC.delegate = self secondChildVC = SecondChildVC() setViewControllers([firstVC], direction: .forward, animated: true) } // MARK: - FirstChildVCDelegate func firstChildDidUpdateData(_ data: String) { secondChildVC?.receiveData(data) } // MARK: - UIPageViewControllerDataSource func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { return viewController is SecondChildVC ? FirstChildVC() : nil } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { return viewController is FirstChildVC ? secondChildVC : nil } } // Second child VC: Receive data from the parent class SecondChildVC: UIViewController { func receiveData(_ data: String) { // Process or display the data print("Received data: \(data)") } }
3. Closures (Concise One-Way Passing)
If you prefer a more lightweight alternative to delegates, closures work great for simple data passing. The parent VC sets up the closure in the first child, which triggers updates to the second child.
// First child VC: Define a closure property class FirstChildVC: UIViewController { var onDataUpdated: ((String) -> Void)? @IBAction func sendDataButtonTapped(_ sender: UIButton) { onDataUpdated?("Data from First VC") } } // Parent UIPageViewController: Wire up the closure class ParentPageVC: UIPageViewController, UIPageViewControllerDataSource { private var secondChildVC: SecondChildVC? override func viewDidLoad() { super.viewDidLoad() dataSource = self let firstVC = FirstChildVC() secondChildVC = SecondChildVC() // Set up the closure to pass data to the second VC firstVC.onDataUpdated = { [weak self] data in self?.secondChildVC?.updateWithData(data) } setViewControllers([firstVC], direction: .forward, animated: true) } // MARK: - UIPageViewControllerDataSource // (Same data source methods as the delegate example above) } // Second child VC: Handle the incoming data class SecondChildVC: UIViewController { func updateWithData(_ data: String) { print("Received data: \(data)") } }
Bonus: Trigger Passing On Page Switch Completion
If you need to pass data exactly when the page switch finishes, use the UIPageViewControllerDelegate method didFinishAnimating to detect the transition completion and trigger data transfer.
extension ParentPageVC: UIPageViewControllerDelegate { override func viewDidLoad() { super.viewDidLoad() delegate = self // Don't forget to set the delegate! } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed, let currentVC = pageViewController.viewControllers?.first else { return } // Pass data from the previous VC to the current one (or vice versa) if currentVC is SecondChildVC, let firstVC = previousViewControllers.first as? FirstChildVC { secondChildVC?.receiveData(firstVC.someLocalData) } else if currentVC is FirstChildVC, let secondVC = previousViewControllers.first as? SecondChildVC { // Reverse data passing if needed (currentVC as? FirstChildVC)?.receiveData(secondVC.someLocalData) } } }
Quick Recommendation
- Use a shared data manager if both VCs need ongoing access to the same state.
- Pick delegates or closures for simple, explicit one-way flows.
- Tie data passing to the
didFinishAnimatingmethod if you need it synchronized with page transitions.
内容的提问来源于stack exchange,提问作者user9557173

