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

SwiftUI如何让全屏加载视图同时覆盖底部TabBar

解决SwiftUI加载视图无法覆盖TabBar的方案

根因分析

你当前的实现加载视图无法覆盖TabBar的核心原因是:progressView修饰器被添加到了TabView的子页面上,子页面的渲染范围默认不包含底部TabBar区域,所以加载视图只能覆盖页面自身内容。

实现方案(推荐)

我们可以将加载修饰器移动到TabView的父层级,通过全局状态控制加载状态,即可实现覆盖全屏幕(包含TabBar)的效果。

步骤1:创建全局加载状态管理类

新增一个可全局监听的加载状态管理类,统一管控全局加载状态:

import SwiftUI
import Combine

class LoaderState: ObservableObject {
    static let shared = LoaderState()
    @Published var isLoading = false
    // 可扩展和你原有修饰器匹配的参数
    @Published var loadingText: String?
    @Published var blur: Bool = false
    @Published var loaderColor: Color = .white
}

步骤2:将加载修饰器挂载到RootTabView外层

修改RootTabView代码,把progressView修饰器加到TabView的外层,绑定全局加载状态:

struct RootTabView: View {
    @StateObject private var loaderState = LoaderState.shared
    var body: some View {
        TabView {
            AddEverydayExpense()
                .tabItem {
                    Label("First", systemImage: "1.circle")
                }
            AddInvestment()
                .tabItem {
                    Label("Second", systemImage: "2.circle")
                }
        }
        // 注入全局状态到所有子页面
        .environmentObject(loaderState)
        // 加载修饰器挂载到TabView外层,覆盖范围包含TabBar
        .progressView(
            isShowing: $loaderState.isLoading,
            text: loaderState.loadingText,
            blur: loaderState.blur,
            loaderColor: loaderState.loaderColor
        )
    }
}

步骤3:业务页面绑定全局加载状态

修改AddEverydayExpense代码,移除页面层级的progressView修饰器,把业务VM的加载状态同步到全局加载状态:

struct AddEverydayExpense: View {
    // 注入全局加载状态
    @EnvironmentObject private var loaderState: LoaderState
    @ObservedObject private var model = AddEverydayExpenseVM()
    // 原有属性保持不变
    @State private var description: String = ""
    @State private var cost: String = ""
    @State private var date: Date = Date()
    @State private var essential: Bool = false
    @State private var month: Month?
    @State private var category: Category?
    private var isButtonDisabled: Bool {
        return description.isEmpty ||
            cost.isEmpty ||
            month == nil ||
            category == nil
    }
    
    var body: some View {
        NavigationView {
            VStack {
                Form {
                    // 原有表单代码完全不变
                    Section {
                        TextField("", text: $description, prompt: Text("Descrizione"))
                        TextField("", text: $cost, prompt: Text("10€"))
                            .keyboardType(.numbersAndPunctuation)
                        DatePicker(date.string(withFormat: "EEEE"), selection: $date)
                        HStack {
                            CheckboxView(checked: $essential)
                            Text("È considerata una spesa essenziale?")
                        }
                        .onTapGesture {
                            essential.toggle()
                        }
                    }
                    Section {
                        Picker(month?.name ?? "Mese di riferimento", selection: $month) {
                            ForEach(model.months) { month in
                                Text(month.name).tag(month as? Month)
                            }
                        }
                        Picker(category?.name ?? "Categoria", selection: $category) {
                            ForEach(model.categories) { category in
                                Text(category.name).tag(category as? Category)
                            }
                        }
                    }
                    Section {
                        Button("Invia".uppercased()) { print("Button") }
                        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                        .font(.headline)
                        .listRowBackground(isButtonDisabled ? Color.gray.opacity(0.5) : Color.blue)
                        .foregroundColor(Color.white.opacity(isButtonDisabled ? 0.5 : 1))
                        .disabled(!isButtonDisabled)
                    }
                }
                Spacer()
            }
            .navigationTitle("Aggiungi Spesa")
        }
        // 移除原来页面层级的progressView修饰器
        // 监听VM的加载状态,同步到全局
        .onReceive(model.$isFetching) { isFetching in
            loaderState.isLoading = isFetching
            // 需要自定义其他加载参数也可以在这里赋值
            loaderState.blur = true
        }
    }
}

优化建议

你原有ProgressViewModifier里的withAnimation用法不符合规范,可以把动画效果加到加载视图的转场里,修改如下:

func body(content: Content) -> some View {
    ZStack { 
        content
        if isShowing {
            showProgressView()
                .transition(.opacity.animation(.easeInOut(duration: 0.2)))
        }
    }
}

这样加载视图出现和消失都会有平滑的淡入淡出效果。


内容的提问来源于stack exchange,提问作者Andrea Mario Lufino

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 07:09:02