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

如何创建适配全屏及旋转的UIPageViewController的SwiftUI包装器?

兼容iOS 15+的SwiftUI ScrollView分页方案

如果没法用iOS 17新增的分页修饰器,下面两种方案可以完美适配iOS 15及更高版本,覆盖不同场景需求:

方法一:纯SwiftUI手势驱动分页

不用依赖UIKit,全用SwiftAPI实现,适合轻量分页场景。核心是通过拖拽手势计算偏移,结合滚动偏移量控制页面切换。

import SwiftUI

struct PagingScrollView<Content: View>: View {
    let pageCount: Int
    let content: (Int) -> Content
    @State private var currentPage = 0
    @State private var offsetX: CGFloat = 0
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 0) {
                ForEach(0..<pageCount, id: \.self) { index in
                    content(index)
                        .frame(width: UIScreen.main.bounds.width)
                }
            }
        }
        .content.offset(x: offsetX)
        .gesture(
            DragGesture()
                .onChanged { value in
                    // 拖拽时实时跟随手指移动
                    offsetX = value.translation.width - CGFloat(currentPage) * UIScreen.main.bounds.width
                }
                .onEnded { value in
                    // 结束拖拽时判断切换方向
                    let pageOffset = value.translation.width / UIScreen.main.bounds.width
                    let newPage = currentPage - Int(pageOffset.rounded())
                    currentPage = max(0, min(newPage, pageCount - 1))
                    // 平滑滚动到目标页
                    withAnimation {
                        offsetX = -CGFloat(currentPage) * UIScreen.main.bounds.width
                    }
                }
        )
        .onAppear {
            // 初始定位到第一页
            offsetX = -CGFloat(currentPage) * UIScreen.main.bounds.width
        }
    }
}

// 使用示例
struct ContentView: View {
    var body: some View {
        PagingScrollView(pageCount: 3) { index in
            Color(
                red: Double.random(in: 0...1),
                green: Double.random(in: 0...1),
                blue: Double.random(in: 0...1)
            )
            .overlay(
                Text("Page \(index + 1)")
                    .font(.largeTitle)
                    .foregroundColor(.white)
            )
        }
    }
}

方法二:包装UIPageViewController(更稳定)

如果需要原生级的分页体验(比如自动吸附、边缘弹跳控制),直接把UIKit的UIPageViewController桥接到SwiftUI,兼容性和稳定性拉满。

import SwiftUI
import UIKit

struct PageViewController<Page: View>: UIViewControllerRepresentable {
    var pages: [Page]
    @Binding var currentPage: Int
    
    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageVC = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal
        )
        pageVC.dataSource = context.coordinator
        pageVC.delegate = context.coordinator
        return pageVC
    }
    
    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        let currentVC = UIHostingController(rootView: pages[currentPage])
        pageViewController.setViewControllers([currentVC], direction: .forward, animated: true)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PageViewController
        
        init(_ parent: PageViewController) {
            self.parent = parent
        }
        
        // 上一页逻辑
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let index = parent.pages.firstIndex(where: {
                (viewController as? UIHostingController<Page>)?.rootView === $0
            }) else { return nil }
            return index > 0 ? UIHostingController(rootView: parent.pages[index - 1]) : nil
        }
        
        // 下一页逻辑
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = parent.pages.firstIndex(where: {
                (viewController as? UIHostingController<Page>)?.rootView === $0
            }) else { return nil }
            return index < parent.pages.count - 1 ? UIHostingController(rootView: parent.pages[index + 1]) : nil
        }
        
        // 更新当前页码
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            guard completed,
                  let currentVC = pageViewController.viewControllers?.first,
                  let index = parent.pages.firstIndex(where: {
                      (currentVC as? UIHostingController<Page>)?.rootView === $0
                  }) else { return }
            parent.currentPage = index
        }
    }
}

// 使用示例
struct ContentView: View {
    @State private var currentPage = 0
    
    var body: some View {
        PageViewController(
            pages: [
                Color.red.overlay(Text("Page 1").font(.largeTitle).foregroundColor(.white)),
                Color.blue.overlay(Text("Page 2").font(.largeTitle).foregroundColor(.white)),
                Color.green.overlay(Text("Page 3").font(.largeTitle).foregroundColor(.white))
            ],
            currentPage: $currentPage
        )
    }
}

内容的提问来源于stack exchange,提问作者wristbands

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:52:38