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
相关产品推荐
相关产品推荐

