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

如何在NavigationStack所有视图的导航栏下方叠加内容?

实现NavigationStack全局导航栏下方进度条

要实现所有视图上方、导航栏正下方的全局进度条,同时适配iPhone/iPad横竖屏,不需要在每个视图硬编码,可通过以下方案解决:

核心思路

  1. 用PreferenceKey捕获导航栏的实际高度(适配不同设备、横竖屏的动态变化)
  2. 将进度条放在NavigationStack的overlay层,实现全局显示
  3. 通过共享ViewModel管理进度状态,跨视图同步更新进度

完整代码实现

1. 定义捕获导航栏高度的PreferenceKey

struct NavigationBarHeightKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())
    }
}

2. 进度管理ViewModel

class NavigationStackViewModel: ObservableObject {
    @Published var currentStep = 1
    let totalSteps = 3
    
    // 计算进度比例
    var progress: CGFloat {
        CGFloat(currentStep) / CGFloat(totalSteps)
    }
    
    // 步骤切换方法
    func nextStep() {
        guard currentStep < totalSteps else { return }
        currentStep += 1
    }
    
    func prevStep() {
        guard currentStep > 1 else { return }
        currentStep -= 1
    }
}

3. 主视图(带全局进度条)

struct NavigationStackEntryView: View {
    @StateObject var viewModel = NavigationStackViewModel()
    @Environment(\.dismiss) var dismiss
    @State private var navigationBarHeight: CGFloat = 0
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(spacing: 20) {
                    NavigationLink("跳转到第2个视图") {
                        SecondView(viewModel: viewModel)
                            .onAppear { viewModel.nextStep() }
                    }
                    
                    Text("当前步骤:\(viewModel.currentStep)/\(viewModel.totalSteps)")
                }
                .padding()
            }
            .navigationTitle("第一个视图")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .automatic) {
                    Button("取消") {
                        dismiss()
                    }
                }
            }
            // 捕获导航栏高度
            .background(
                GeometryReader { proxy in
                    Color.clear
                        .preference(key: NavigationBarHeightKey.self, value: proxy.size.height)
                }
            )
            .onPreferenceChange(NavigationBarHeightKey.self) { height in
                navigationBarHeight = height
            }
            // 全局叠加进度条,定位在导航栏下方
            .overlay(alignment: .top) {
                ProgressView(value: viewModel.progress)
                    .progressViewStyle(LinearProgressViewStyle(tint: .blue))
                    .frame(height: 8)
                    .padding(.top, navigationBarHeight)
            }
        }
    }
}

4. 子视图示例(SecondView)

struct SecondView: View {
    @ObservedObject var viewModel: NavigationStackViewModel
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                NavigationLink("跳转到第3个视图") {
                    ThirdView(viewModel: viewModel)
                        .onAppear { viewModel.nextStep() }
                }
                
                Button("返回上一步") {
                    viewModel.prevStep()
                    dismiss()
                }
                
                Text("当前步骤:\(viewModel.currentStep)/\(viewModel.totalSteps)")
            }
            .padding()
        }
        .navigationTitle("第二个视图")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .automatic) {
                Button("取消") {
                    dismiss()
                }
            }
        }
        // 横竖屏切换时重新捕获导航栏高度
        .background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: NavigationBarHeightKey.self, value: proxy.size.height)
            }
        )
    }
}

5. 子视图示例(ThirdView)

struct ThirdView: View {
    @ObservedObject var viewModel: NavigationStackViewModel
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                Button("返回上一步") {
                    viewModel.prevStep()
                    dismiss()
                }
                
                Text("当前步骤:\(viewModel.currentStep)/\(viewModel.totalSteps)")
                Text("流程已完成!")
            }
            .padding()
        }
        .navigationTitle("第三个视图")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .automatic) {
                Button("完成") {
                    dismiss()
                }
            }
        }
        .background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: NavigationBarHeightKey.self, value: proxy.size.height)
            }
        )
    }
}

关键说明

  • 动态高度捕获:通过GeometryReader和PreferenceKey实时获取导航栏的实际高度,包括顶部安全区域,完美适配横竖屏和不同设备。
  • 全局overlay:进度条直接添加在NavigationStack的overlay中,所有栈内视图自动继承,无需重复编写。
  • 状态同步:通过共享ViewModel,所有视图的进度状态保持一致,切换视图时自动更新进度条。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:12:34