Home > Back-end >  Show full screen view overlaying also TabBar
Show full screen view overlaying also TabBar

Time:09-28

I'm trying to show a view with a loader in full screen. I want also to overlay the TabBar, but I don't know how to do it. Let me show my code.

This is 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)
    }
}

This is the RootTabView, the one containing the 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()
    }
}

This is my view.

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)
    }
}

As you can see, there is the line .progressView(isShowing: $model.isFetching, blur: true) that does the magic. The problem is that the loader is only shown on the current view, but not on the tab. iPhone.

How can I achieve the result?

CodePudding user response:

If you want the progress view to cover the entire view (including the tab bar), it has to be in the view hierarchy at or above the TabBar. Right now, it's below the TabBar in the child views.

Because the state will need to be passed up to the parent (the owner of the TabBar), you'll need some sort of state management that you can pass down to the children. This could mean just passing a Binding to a @State. I've chosen to show how to achieve this with an ObservableObject passed down the hierarchy using an @EnvironmentObject so that you don't have to explicitly pass the dependency.

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
        }
    }
}
  • Related