如何通过Segmented Control控制容器内的UIPageViewController?
解决容器视图内UIPageViewController与主VC分段控件的双向联动问题
要实现容器内UIPageViewController和主VC分段控件的双向同步,核心是解决父子视图控制器之间的通信问题。以下是两种适配iOS 12、Swift 5的可行方案:
方法一:代理模式(推荐,代码解耦性更强)
1. 为UIPageViewController定义代理协议
先给你的UIPageViewController创建子类(假设命名为CustomPageViewController),在其中定义代理协议,用来通知主VC当前页面的索引变化:
import UIKit // 定义代理协议,用于传递页面索引变化 protocol CustomPageViewControllerDelegate: AnyObject { func pageViewController(_ pageVC: CustomPageViewController, didSwitchToIndex index: Int) } class CustomPageViewController: UIPageViewController { // 代理对象,用weak避免循环引用 weak var pageDelegate: CustomPageViewControllerDelegate? // 存储所有子VC的数组 private var childVCs: [UIViewController] = [] // 当前页面索引 private var currentIndex = 0 override func viewDidLoad() { super.viewDidLoad() // 设置自身为数据源和代理 dataSource = self delegate = self // 从Storyboard初始化子VC(替换成你实际的StoryboardID) let storyboard = UIStoryboard(name: "Main", bundle: nil) let vc1 = storyboard.instantiateViewController(withIdentifier: "FirstVC") let vc2 = storyboard.instantiateViewController(withIdentifier: "SecondVC") let vc3 = storyboard.instantiateViewController(withIdentifier: "ThirdVC") childVCs = [vc1, vc2, vc3] // 默认显示第一个页面 setViewControllers([childVCs[0]], direction: .forward, animated: false, completion: nil) } // 提供外部调用方法,跳转到指定索引的页面 func scrollToPage(at index: Int) { guard index >= 0, index < childVCs.count, index != currentIndex else { return } let direction: UIPageViewController.NavigationDirection = index > currentIndex ? .forward : .reverse setViewControllers([childVCs[index]], direction: direction, animated: true, completion: nil) currentIndex = index } } // 实现UIPageViewController的数据源和代理方法 extension CustomPageViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate { // 返回上一个页面 func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let index = childVCs.firstIndex(of: viewController), index > 0 else { return nil } return childVCs[index - 1] } // 返回下一个页面 func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let index = childVCs.firstIndex(of: viewController), index < childVCs.count - 1 else { return nil } return childVCs[index + 1] } // 页面滑动完成后,通知主VC更新分段控件 func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed, let currentVC = pageViewController.viewControllers?.first, let index = childVCs.firstIndex(of: currentVC) else { return } currentIndex = index pageDelegate?.pageViewController(self, didSwitchToIndex: index) } }
2. 主VC实现代理,处理双向联动
在主VC中,关联分段控件的IBOutlet和点击事件,并设置UIPageViewController的代理:
import UIKit class MainViewController: UIViewController, CustomPageViewControllerDelegate { @IBOutlet weak var mainSegmentedControl: UISegmentedControl! // 存储容器内的UIPageViewController实例 private var pageVC: CustomPageViewController? override func viewDidLoad() { super.viewDidLoad() // 从子VC中找到CustomPageViewController for child in children { if let pageViewController = child as? CustomPageViewController { pageVC = pageViewController pageViewController.pageDelegate = self break } } } // 分段控件点击事件:跳转到对应页面 @IBAction func segmentedControlTapped(_ sender: UISegmentedControl) { pageVC?.scrollToPage(at: sender.selectedSegmentIndex) } // 实现代理方法:同步分段控件选中状态 func pageViewController(_ pageVC: CustomPageViewController, didSwitchToIndex index: Int) { mainSegmentedControl.selectedSegmentIndex = index } }
方法二:闭包回调(代码更简洁)
如果觉得代理模式繁琐,可以用闭包来传递页面索引变化:
1. 在UIPageViewController子类中添加闭包属性
class CustomPageViewController: UIPageViewController { // 闭包,用于传递当前页面索引 var indexChangedHandler: ((Int) -> Void)? // ... 其他初始化、数据源代码和方法一一致 // 页面滑动完成时调用闭包 func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed, let currentVC = pageViewController.viewControllers?.first, let index = childVCs.firstIndex(of: currentVC) else { return } currentIndex = index indexChangedHandler?(index) } }
2. 主VC中设置闭包并处理点击事件
class MainViewController: UIViewController { @IBOutlet weak var mainSegmentedControl: UISegmentedControl! private var pageVC: CustomPageViewController? override func viewDidLoad() { super.viewDidLoad() for child in children { if let pageViewController = child as? CustomPageViewController { pageVC = pageViewController // 设置闭包,同步分段控件状态 pageViewController.indexChangedHandler = { [weak self] index in self?.mainSegmentedControl.selectedSegmentIndex = index } break } } } // 分段控件点击事件 @IBAction func segmentedControlTapped(_ sender: UISegmentedControl) { pageVC?.scrollToPage(at: sender.selectedSegmentIndex) } }
关键注意事项
- 确保在Storyboard中给UIPageViewController设置正确的子类(
CustomPageViewController),且子VC的StoryboardID与代码中一致。 - 闭包和代理中都要使用
weak self,避免循环引用导致内存泄漏。 - iOS 12完全支持以上两种方案,无需额外兼容性处理。
内容的提问来源于stack exchange,提问作者Luiz Fernando Andrade
相关产品推荐
相关产品推荐

