显示全屏视图覆盖 TabBar

Show full screen view overlaying also TabBar

我正在尝试全屏显示带有加载程序的视图。我也想覆盖 TabBar,但我不知道该怎么做。让我展示一下我的代码。

这是ProgressViewModifier

// MARK: - View - Extension

extension View {
    
    /// Show a loader binded to `isShowing` parameter.
    /// - Parameters:
    ///   - isShowing: `Bool` value to indicate if the loader is to be shown or not.
    ///   - text: An optional text to show below the spinning circle.
    ///   - color: The color of the spinning circle.
    /// - Returns: The loader view.
    func progressView(
        isShowing: Binding <Bool>,
        backgroundColor: Color = .black,
        dimBackground: Bool = false,
        text : String? = nil,
        loaderColor : Color = .white,
        scale: Float = 1,
        blur: Bool = false) -> some View {
            
        self.modifier(ProgressViewModifier(
            isShowing: isShowing,
            backgroundColor: backgroundColor,
            dimBackground: dimBackground,
            text: text,
            loaderColor: loaderColor,
            scale: scale,
            blur: blur)
        )
    }
}


// MARK: - ProgressViewModifier

struct ProgressViewModifier : ViewModifier {
    @Binding var isShowing : Bool
    
    var backgroundColor: Color
    var dimBackground: Bool
    var text : String?
    var loaderColor : Color
    var scale: Float
    var blur: Bool
    
    func body(content: Content) -> some View {
    
        ZStack { content

            if isShowing {
                withAnimation {
                    showProgressView()
                }
            }
        }
    }
}


// MARK: - Private methods

extension ProgressViewModifier {
    
    private func showProgressView() -> some View {
        ZStack {
            Rectangle()
                .fill(backgroundColor.opacity(0.7))
                .ignoresSafeArea()
                .background(.ultraThinMaterial)
            VStack (spacing : 20) {
                if isShowing {
                    ProgressView()
                        .tint(loaderColor)
                        .scaleEffect(CGFloat(scale))
                    if text != nil {
                        Text(text!)
                        .foregroundColor(.black)
                        .font(.headline)
                    }
                }
            }
            .background(.clear)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

这是 RootTabView,包含 TabBar 的那个。

struct RootTabView: View {
    var body: some View {
        TabView {
            AddEverydayExpense()
                .tabItem {
                    Label("First", systemImage: "1.circle")
                }
            AddInvestment()
                .tabItem {
                    Label("Second", systemImage: "2.circle")
                }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        RootTabView()
    }
}

这是我的看法。

struct AddEverydayExpense: View {
    
    @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(isShowing: $model.isFetching, blur: true)
    }
}

如您所见,.progressView(isShowing: $model.isFetching, blur: true) 行发挥了神奇作用。问题是加载程序只显示在当前视图中,而不是选项卡上。 .

我怎样才能达到这个结果?

如果您希望进度视图覆盖整个视图(包括标签栏),它必须位于 TabBar 或以上的视图层次结构中。现在,它位于子视图中 TabBar 的下方。

因为状态需要传递给父级(TabBar 的所有者),所以您需要某种可以传递给子级的状态管理。这可能意味着只是将 Binding 传递给 @State。我选择展示如何通过使用 @EnvironmentObject 向下传递层次结构的 ObservableObject 来实现这一点,这样您就不必显式传递依赖关系。

class ProgressManager : ObservableObject {
    @Published var inProgress = false
}

struct ContentView : View {
    @StateObject private var progressManager = ProgressManager()
    
    var body: some View {
        TabView {
            AddEverydayExpense()
                .tabItem {
                    Label("First", systemImage: "1.circle")
                }
            AddInvestment()
                .tabItem {
                    Label("Second", systemImage: "2.circle")
                }
        }
        .environmentObject(progressManager)
        .progressView(isShowing: $progressManager.inProgress) //<-- Note that this is outside of the `TabBar`
    }
}

struct AddEverydayExpense : View {
    @EnvironmentObject private var progressManager : ProgressManager
    
    var body: some View {
        Button("Progress") {
            progressManager.inProgress = true
        }
    }
}