You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中无法实现翻页效果?求类Apple Books逼真翻页方案

在SwiftUI中实现类似UIKit的.pageCurl翻页效果

要实现Apple Books那样的逼真拖动翻页效果,你当前用TabView加3D旋转的方案只能完成基础的页面翻转,没法模拟书页卷曲的物理交互细节。下面是几个可行的实现思路:

1. 直接封装UIKit的UIPageViewController(最省心的原生效果)

既然UIKit已经提供了成熟的.pageCurl过渡样式,直接把UIPageViewController包装成SwiftUI组件是最快的方式,能直接获得和系统一致的拖动翻页体验。

代码示例:

struct PageCurlView: UIViewControllerRepresentable {
    var pageCount: Int
    @Binding var currentPage: Int
    
    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageVC = UIPageViewController(
            transitionStyle: .pageCurl,
            navigationOrientation: .horizontal,
            options: nil
        )
        pageVC.dataSource = context.coordinator
        pageVC.delegate = context.coordinator
        pageVC.setViewControllers(
            [makePageViewController(index: 0)],
            direction: .forward,
            animated: false
        )
        return pageVC
    }
    
    func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
        // 根据currentPage更新页面的逻辑可按需补充
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    func makePageViewController(index: Int) -> UIViewController {
        let hostingVC = UIHostingController(rootView: BeforeThePage(imageName: "C1 Slice \(index + 1)"))
        hostingVC.view.tag = index
        hostingVC.view.backgroundColor = .white
        return hostingVC
    }
    
    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PageCurlView
        
        init(parent: PageCurlView) {
            self.parent = parent
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            let index = viewController.view.tag
            return index > 0 ? parent.makePageViewController(index: index - 1) : nil
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            let index = viewController.view.tag
            return index < parent.pageCount - 1 ? parent.makePageViewController(index: index + 1) : nil
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed, let currentVC = pageViewController.viewControllers?.first {
                parent.currentPage = currentVC.view.tag
            }
        }
    }
}

// 在SwiftUI中使用
struct Episode1View: View {
    @State private var currentPage = 0
    let contentCount = 11

    var body: some View {
        PageCurlView(pageCount: contentCount, currentPage: $currentPage)
            .navigationBarHidden(true)
    }
}

这个方案直接复用UIKit的原生实现,不需要自己处理复杂的物理效果,拖动体验和系统应用完全一致。

2. 纯SwiftUI自定义翻页效果(需手动模拟物理)

如果必须完全用SwiftUI实现,需要结合DragGesture跟踪手指位置,动态计算3D变换、阴影和形状扭曲,模拟书页卷曲的效果。核心思路:

  • 用GestureState记录拖动偏移量,计算翻页角度和卷曲程度
  • 对当前页和下一页应用不同的rotation3DEffect与offset
  • 添加动态阴影,模拟书页卷曲时的光影变化
  • 处理手势结束时的动画,完成翻页或回弹

简化示例代码:

struct CustomPageCurlView: View {
    @State private var currentPage = 0
    @GestureState private var dragOffset: CGSize = .zero
    let contentCount = 11
    private let screenWidth = UIScreen.main.bounds.width
    
    var body: some View {
        ZStack {
            // 下一页
            BeforeThePage(imageName: "C1 Slice \(min(currentPage + 1, contentCount))")
            
            // 当前页
            BeforeThePage(imageName: "C1 Slice \(currentPage + 1)")
                .rotation3DEffect(
                    .degrees(-Double(dragOffset.width / screenWidth) * 90),
                    axis: (x: 0, y: 1, z: 0),
                    anchor: .trailing,
                    perspective: 0.8
                )
                .offset(x: dragOffset.width)
                .shadow(color: .black.opacity(0.3), radius: 10, x: dragOffset.width > 0 ? -5 : 5)
                .gesture(
                    DragGesture()
                        .updating($dragOffset) { value, state, _ in
                            state = value.translation
                        }
                        .onEnded { value in
                            let threshold = screenWidth / 3
                            if value.translation.width < -threshold {
                                currentPage = min(currentPage + 1, contentCount - 1)
                            } else if value.translation.width > threshold {
                                currentPage = max(currentPage - 1, 0)
                            }
                        }
                )
        }
        .navigationBarHidden(true)
    }
}

这个方案需要反复调整参数优化逼真度,比如透视值、阴影强度、旋转角度计算逻辑,还可以通过clipShape结合Path实现书页边缘的卷曲变形,但效果很难完全达到UIKit原生.pageCurl的自然程度。

总结

  • 追求快速实现和原生体验,优先选择封装UIKit的UIPageViewController方案
  • 必须纯SwiftUI实现的话,需要投入较多精力调整手势和动画参数,模拟物理效果

内容的提问来源于stack exchange,提问作者Antonio A. Chavez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:55:53