如何为UIPageViewController添加视差滚动效果?
为UIPageViewController添加水平视差滚动效果
首先先纠正一个点:UIPageViewController并不是继承自UIScrollView,它本质是一个UIViewController,不过当你使用.scroll过渡样式(这也是水平分页常用的样式)时,它的内部会包含一个UIScrollView作为子视图——这正是我们实现视差的关键。
下面是具体的实现步骤,亲测可行:
1. 获取内部的UIScrollView
因为这个scrollView是UIPageViewController的私有子视图,我们需要遍历它的子视图来找到:
private func findInternalScrollView() -> UIScrollView? { return view.subviews.first { $0 is UIScrollView } as? UIScrollView }
在viewDidLoad里调用这个方法,拿到scrollView后设置它的delegate为当前UIPageViewController(记得要让你的UIPageViewController遵守UIScrollViewDelegate协议):
override func viewDidLoad() { super.viewDidLoad() // 其他初始化逻辑... if let scrollView = findInternalScrollView() { scrollView.delegate = self } }
2. 监听滚动事件,实现视差逻辑
在scrollViewDidScroll方法里,我们可以根据滚动的偏移量,计算当前页面的滚动进度,然后调整页面内元素的位置来实现视差。
假设你的每个页面ViewController(比如叫ParallaxPageVC)里有一个需要做视差的背景视图parallaxBackgroundView,可以这样写:
func scrollViewDidScroll(_ scrollView: UIScrollView) { let pageWidth = scrollView.bounds.width // 计算滚动的进度:0~1之间,代表从当前页到下一页的滚动比例 let fractionalOffset = scrollView.contentOffset.x / pageWidth let currentPageIndex = Int(floor(fractionalOffset)) let scrollProgress = fractionalOffset - CGFloat(currentPageIndex) // 处理当前页面的视差 if let currentPageVC = viewControllers?.first as? ParallaxPageVC { // 让背景反向移动,速度是页面滚动的50%,你可以调整这个系数(0.5)来控制视差强度 let backgroundOffsetX = -scrollProgress * pageWidth * 0.5 currentPageVC.parallaxBackgroundView.transform = CGAffineTransform(translationX: backgroundOffsetX, y: 0) } // 处理下一个页面的视差(当正在滚动到下一页时) if scrollProgress > 0, let nextPageVC = dataSource?.pageViewController(self, viewControllerAfter: viewControllers?.first) as? ParallaxPageVC { let backgroundOffsetX = (1 - scrollProgress) * pageWidth * 0.5 nextPageVC.parallaxBackgroundView.transform = CGAffineTransform(translationX: backgroundOffsetX, y: 0) } }
3. 页面切换完成后重置视图状态
为了避免下次滚动时出现异常,在页面切换动画完成后,要把视图的transform重置为初始状态:
override func didFinishAnimating(_ finished: Bool, previousViewControllers: [UIViewController], transitionCompleted: Bool) { super.didFinishAnimating(finished, previousViewControllers: previousViewControllers, transitionCompleted: transitionCompleted) guard transitionCompleted else { return } // 重置当前页面的背景视图 if let currentVC = viewControllers?.first as? ParallaxPageVC { currentVC.parallaxBackgroundView.transform = .identity } // 重置上一个页面的背景视图 if let previousVC = previousViewControllers.first as? ParallaxPageVC { previousVC.parallaxBackgroundView.transform = .identity } }
一些注意事项
- 这个方案依赖UIPageViewController内部的scrollView结构,虽然目前iOS版本(包括最新的)都保持这个结构,但未来苹果可能会调整,所以需要在不同iOS版本上做兼容性测试。
- 视差强度可以通过调整代码中的系数(比如0.5)来控制:系数越大,背景移动的距离越大,视差效果越明显;系数越小,效果越平缓。
- 确保你的页面ViewController的视图已经加载完成,不然
parallaxBackgroundView可能为nil——可以在页面VC的viewDidLoad里提前初始化这个视图。 - 如果你的UIPageViewController用的是
pageCurl过渡样式,这个方法不适用,因为内部没有scrollView,不过你说的是水平方向,应该用的是.scroll样式。
总的来说,给UIPageViewController加视差滚动是完全可行的,核心就是利用它内部的scrollView监听滚动,然后动态调整页面内元素的位置~
内容的提问来源于stack exchange,提问作者Moritz
相关产品推荐
相关产品推荐

