SwiftUI中如何实现NavigationView转场联动自定义进度视图动画?
在SwiftUI中实现与导航转场联动的进度视图(支持侧滑实时更新)
完全可行。虽然SwiftUI原生的NavigationStack/NavigationView没有直接暴露转场进度的API,但可以通过桥接UIKit的UINavigationController实现,思路和你在UIKit中的做法一致,只需用SwiftUI的视图包装器和ObservableObject传递状态。
实现步骤
1. 定义进度管理的ObservableObject
这个类负责存储导航栈深度、转场进度,并提供状态更新逻辑,让SwiftUI视图能响应变化:
import SwiftUI import UIKit class NavigationProgressManager: ObservableObject { // 当前导航栈深度(根视图为0,每push一个页面加1) @Published var stackDepth: Int = 0 // 转场实时进度(0~1,对应转场完成百分比) @Published var transitionProgress: CGFloat = 0.0 private weak var navigationController: UINavigationController? func setup(with navController: UINavigationController) { self.navigationController = navController updateStackDepth() // 监听侧滑返回手势 navController.interactivePopGestureRecognizer?.addTarget(self, action: #selector(handlePopGesture)) // 监听导航转场完成事件 NotificationCenter.default.addObserver( self, selector: #selector(handleTransitionComplete), name: NSNotification.Name("UINavigationControllerDidShowViewController"), object: navController ) } @objc private func handlePopGesture(_ gesture: UIGestureRecognizer) { guard let coordinator = navigationController?.transitionCoordinator else { return } switch gesture.state { case .began, .changed: transitionProgress = coordinator.percentageComplete case .ended, .cancelled: transitionProgress = 0.0 updateStackDepth() default: break } } @objc private func handleTransitionComplete(_ notification: Notification) { updateStackDepth() transitionProgress = 0.0 } private func updateStackDepth() { stackDepth = navigationController?.viewControllers.count ?? 0 } }
2. 创建自定义导航控制器的SwiftUI包装器
通过UIViewControllerRepresentable将UIKit的UINavigationController接入SwiftUI,并关联进度管理器:
struct CustomNavigationView<Content: View>: UIViewControllerRepresentable { @ViewBuilder let content: () -> Content @ObservedObject var progressManager: NavigationProgressManager func makeUIViewController(context: Context) -> UINavigationController { let navController = UINavigationController() let rootVC = UIHostingController(rootView: content()) navController.setViewControllers([rootVC], animated: false) progressManager.setup(with: navController) return navController } func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { if let rootVC = uiViewController.viewControllers.first as? UIHostingController<Content> { rootVC.rootView = content() } } }
3. 在SwiftUI中绑定进度视图
将进度管理器的状态绑定到自定义ProgressView,实现转场联动:
struct ContentView: View { @StateObject private var progressManager = NavigationProgressManager() var body: some View { VStack(spacing: 16) { // 自定义进度视图,根据栈深度和转场进度计算当前进度 CustomProgressView( totalPages: 3, currentProgress: calculateProgress() ) CustomNavigationView(progressManager: progressManager) { RootPage() } } } private func calculateProgress() -> CGFloat { // 假设总共有3个页面,栈深度从1(根视图)到3(最后一页) let baseProgress = CGFloat(progressManager.stackDepth - 1) / 2 // 侧滑返回时,进度随手势反向变化 let adjustedProgress = baseProgress - progressManager.transitionProgress / 2 return max(0, min(1, adjustedProgress)) } } // 示例自定义进度视图 struct CustomProgressView: View { let totalPages: Int let currentProgress: CGFloat var body: some View { GeometryReader { geo in ZStack(alignment: .leading) { Rectangle() .fill(Color.gray.opacity(0.3)) .frame(height: 8) Rectangle() .fill(Color.blue) .frame(width: geo.size.width * currentProgress, height: 8) .cornerRadius(4) } .cornerRadius(4) } .frame(height: 8) .padding(.horizontal) } } // 示例页面 struct RootPage: View { var body: some View { NavigationStack { VStack { Text("根页面") NavigationLink("进入页面1") { Page1() } } .navigationTitle("首页") } } } struct Page1: View { var body: some View { VStack { Text("页面1") NavigationLink("进入页面2") { Page2() } } .navigationTitle("页面1") } } struct Page2: View { var body: some View { Text("页面2") .navigationTitle("页面2") } }
关键说明
- 代码通过监听
UINavigationController的interactivePopGestureRecognizer和transitionCoordinator,实时获取转场进度并同步到SwiftUI视图。 - 如需支持push转场的进度监听,可以在
UINavigationController的pushViewController方法中扩展逻辑,通过transitionCoordinator获取push的实时进度。 calculateProgress方法需根据你的实际页面数量调整,确保进度计算符合业务预期。
内容的提问来源于stack exchange,提问作者Cameron Porter
相关产品推荐
相关产品推荐

