嵌套UIPageViewController的连续滚动异常问题求助
我完全懂你碰到的这个坑——嵌套的UIPageViewController就是容易出现手势冲突的问题,你现在的情况是Profile里的子PageViewController把向右滑的手势给截胡了,导致外层的MainViewController根本没机会响应返回操作。
问题根源
你在ProfileViewController里给子PageViewController的ScrollView加了freezScrolling方法,当处于第一页(ControllerA)时,强制把contentOffset固定在scrollView.bounds.size.width,这相当于直接“锁死”了向右滑的手势,连外层的PageViewController都没机会接收到这个手势信号。再加上UIPageViewController的手势识别默认是内部ScrollView优先处理,嵌套时就会出现内层拦截、外层无法响应的情况。
解决方案
我们需要通过手势识别代理来控制内层手势的响应时机,让它在特定条件下(当前是ControllerA且向右滑动)把手势“让”给外层的MainViewController。具体修改如下:
1. 修改ProfileViewController
首先给ProfileViewController添加手势代理,移除原来的freezScrolling逻辑,换成手势控制:
final class ProfileViewController: UIViewController, UIGestureRecognizerDelegate { private let pageController: UIPageViewController private lazy var controllers: [UIViewController] = [ControllerA(), ControllerB()] private var currentPageIndex = 0 private var innerScrollView: UIScrollView? // 保存内部ScrollView引用 override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() configurePageController() } func configurePageController() { pageController.dataSource = self pageController.delegate = self pageController.setViewControllers([controllers[0]], direction: .forward, animated: false) // 找到内部ScrollView并设置代理和手势代理 for view in pageController.view.subviews { if let scrollView = view as? UIScrollView { innerScrollView = scrollView scrollView.delegate = self // 给ScrollView的pan手势设置代理 scrollView.panGestureRecognizer.delegate = self break } } } // 控制是否允许多个手势同时识别 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { // 只有当当前是第一页且向右滑动时,允许外层手势同时响应 guard currentPageIndex == 0, let panGesture = gestureRecognizer as? UIPanGestureRecognizer else { return false } return panGesture.velocity(in: view).x > 0 } // 控制手势是否应该开始识别 func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { guard currentPageIndex == 0, let panGesture = gestureRecognizer as? UIPanGestureRecognizer else { return true } let velocity = panGesture.velocity(in: view) // 如果是向右滑动且已经在第一页,不让内层手势触发,交给外层处理 if velocity.x > 0 { return (innerScrollView?.contentOffset.x ?? 0) > 0 } return true } } // 移除原来的freezScrolling相关逻辑 extension ProfileViewController: UIScrollViewDelegate {} // 保留原有的PageViewController数据源和代理逻辑 extension ProfileViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { if let index = controllers.firstIndex(of: viewController), index > 0 { return controllers[index - 1] } return nil } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { if let index = controllers.firstIndex(of: viewController), index < controllers.count - 1 { return controllers[index + 1] } return nil } } extension ProfileViewController: UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed else { return } if let vc = pageController.viewControllers?.first, let index = controllers.firstIndex(of: vc) { currentPageIndex = index } } }
2. 调整MainViewController的ScrollViewDelegate
去掉最后一页的右滑限制,让外层PageViewController能正常响应返回手势:
extension MainViewController: UIScrollViewDelegate { private func freezScrolling(_ scrollView: UIScrollView) { // 只限制第一页的左滑,避免滑出边界 if currentPageIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width { scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0) } // 移除最后一页的右滑限制,这样从Profile返回时能正常触发 } func scrollViewDidScroll(_ scrollView: UIScrollView) { freezScrolling(scrollView) } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { freezScrolling(scrollView) } }
原理说明
- 通过
gestureRecognizerShouldBegin,当处于ControllerA且向右滑动时,阻止内层ScrollView的手势触发,让外层的MainViewController的PageViewController能接收到手势。 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)确保内层和外层手势可以同时识别,避免冲突。- 移除了强制固定contentOffset的逻辑,让手势能正常传递。
这样修改后,当你在ControllerA页面向右滑动时,就会触发外层的页面切换,返回ItemsViewController;而向左滑动时,依然正常切换到ControllerB,完美解决手势拦截的问题。
内容的提问来源于stack exchange,提问作者MohamMad Salah

