先看效果
仿真翻页效果:

普通翻页效果:

实现方案
iOS 中实现翻页效果比较简单,直接使用系统提供的 UIPageViewController 即可做到。
UIPageViewController 是 UIKit 中的分页控制器,它允许用户通过横向或纵向滑动手势在多个页面(ViewController)之间切换,主要配置的两个属性如下:
1)UIPageViewControllerTransitionStyle
.pageCurl:仿真翻页.scroll:类似 UIScrollView 自然滑动
2)UIPageViewControllerNavigationOrientation
.horizontal:左右翻页.vertical:上下翻页
以仿真翻页配置为例子:
class BookReaderViewController: UIViewController { // 模拟书籍数据 private let bookPages = [ "第一章:Swift 的起源nnSwift 是一种由 Apple 开发的强大且直观的编程语言...", "第二章:UIKit 基础nnUIKit 提供了构建 iOS 应用程序所需的关键对象...", "第三章:动画艺术nn核心动画 (Core Animation) 是 iOS 界面流畅的关键...", "第四章:高级翻页nnUIPageViewController 是实现仿真翻页的神器...", "终章:未来展望nn随着 SwiftUI 的普及,声明式 UI 正在改变世界..." ] private var pageViewController: UIPageViewController! override func viewDidLoad() { super.viewDidLoad() setupPageViewController() } private func setupPageViewController() { // 关键设置:transitionStyle = .pageCurl (仿真翻页效果) // navigationOrientation = .horizontal (水平翻页) pageViewController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil) pageViewController.dataSource = self pageViewController.delegate = self // 设置初始页面 if let firstPage = getViewController(at: 0) { pageViewController.setViewControllers([firstPage], direction: .forward, animated: false, completion: nil) } // 将 PageVC 添加到当前 VC addChild(pageViewController) view.addSubview(pageViewController.view) pageViewController.view.frame = view.bounds pageViewController.didMove(toParent: self) // 解决仿真翻页背面颜色问题 (让背面也是纸张色,而不是默认的半透明或白色) // 注意:这是一个比较 Hack 的方法,更完美的做法是自定义背面的 Layer pageViewController.view.backgroundColor = UIColor(red: 248/255, green: 241/255, blue: 227/255, alpha: 1.0) } // 辅助方法:根据索引获取 VC private func getViewController(at index: Int) -> BookPageViewController? { guard index >= 0 && index < bookPages.count else { return nil } return BookPageViewController(index: index, totalPage: bookPages.count, content: bookPages[index]) } } // MARK: - 3. DataSource 实现 (核心逻辑) extension BookReaderViewController: UIPageViewControllerDataSource { // 获取"上一页" func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let currentVC = viewController as? BookPageViewController else { return nil } let previousIndex = currentVC.pageIndex - 1 return getViewController(at: previousIndex) } // 获取"下一页" func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let currentVC = viewController as? BookPageViewController else { return nil } let nextIndex = currentVC.pageIndex + 1 return getViewController(at: nextIndex) } } // MARK: - 4. Delegate (可选,用于处理翻页后的状态) extension BookReaderViewController: UIPageViewControllerDelegate { // 这里可以处理 spineLocation,例如横屏时显示双页 func pageViewController(_ pageViewController: UIPageViewController, spineLocationFor orientation: UIInterfaceOrientation) -> UIPageViewController.SpineLocation { // 手机竖屏通常是单页 (.min) return .min } }
如上不到百行的代码即可实现仿真翻页效果,手势在 UIPageViewController 中会自动处理,外部不用感知。
和 UITableView 使用类似,需要通过 UIPageViewControllerDataSource 来提供上一页和下一页的数据源,通过 UIPageViewControllerDelegate 来感知翻页时机。
UIPageViewControllerDelegate 主要提供三个时机:
1)willTransitionTo:当用户开始滑动翻页的时候触发,系统已经准备好目标页,通过该回调来告诉你将要显示哪个页面(pendingViewControllers)
/// pendingViewControllers: 将要显示的页面 func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController])
2)didFinishAnimating:当用户的翻页动画结束时回调
/// finished: 动画是否完成 /// previousViewControllers: 原来显示的ViewController /// completed: 最终是否翻页成功;比如滑一半又拖回去,不会真正翻页 func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
3)spineLocationFor:控制仿真书本翻页时 “书脊” 的位置,只在 UIPageViewControllerTransitionStyle 为 pageCurl 时有效
/// SpineLocation: /// - none: 没书脊 /// - min: 书脊在左边(单页模式) /// - mid: 书脊在中间(双页模式) /// - max: 书脊在右边(单页模式) func pageViewController(_ pageViewController: UIPageViewController, spineLocationFor orientation: UIInterfaceOrientation) -> UIPageViewController.SpineLocation
如果要配置普通翻页效果,只需要修改 UIPageViewController 的配置即可:
// Options: 设置页面之间的间距 (微信读书一般有 10-20pt 的间距) let options: [UIPageViewController.OptionsKey: Any] = [ .interPageSpacing: 20 ] // 核心修改 1: transitionStyle 改为 .scroll pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: options)
另外翻页手势通常和系统的侧滑返回手势有冲突,可以手动禁用手势来解决;类似微信读书一样,在导航栏出现时才开启侧滑返回手势,否则禁用侧滑返回:
private func updateGesture() { if isNaviBarHidden { // 导航栏隐藏:禁用侧滑,开启翻页手势 navigationController?.interactivePopGestureRecognizer?.isEnabled = false for gesture in pageViewController.gestureRecognizers { gesture.isEnabled = true } } else { // 导航栏显示:开启侧滑,禁用翻页手势 navigationController?.interactivePopGestureRecognizer?.isEnabled = true for gesture in pageViewController.gestureRecognizers { gesture.isEnabled = false } } }