SwiftUI: Full screen View over NavigationBar and TabBar

Viewed 4322

I'm trying to add a full screen View over my app in SwiftUI. The purpose of this is to have a "shade" that fades in that will darken the screen and bring focus to a custom pop-up, disabling content in the background. See below for a visual example:

What I've gotWhat I want

The View that I'm trying to add this shade over is embedded in a complex NavigationView stack (several layers deep, accessed via a NavigationLink) and also has a visible TabBar. So far I've tried embedding the NavigationView in a ZStack and adding a Rectangle() on top but to no avail, the NavigationBar and TabBar still sit on top of this view. I've also tried using using the .zIndex() modifier but this seems to have done nothing.

Any help is much appreciated,

Thanks

2 Answers

You do not need work on zIndex, because you cover the all screen! Even you do not need work on disable your current View for using PopUp, because again PopUp is already on top layer. zIndex would be helpful when you did not cover the screen, here is a way:

struct ContentView: View {
    
    @State private var isPresented: Bool = Bool()
    
    var body: some View {
        
        NavigationView {
            
            VStack {
                
                Button("Show Custom PopUp View") { isPresented.toggle() }

            }
            .navigationTitle("Navigation Title")
            
        }
        .customPopupView(isPresented: $isPresented, popupView: { popupView })
        
    }
    
    var popupView: some View {
        
        RoundedRectangle(cornerRadius: 20.0)
            .fill(Color.white)
            .frame(width: 300.0, height: 200.0)
            .overlay(
                
                Image(systemName: "xmark").resizable().frame(width: 10.0, height: 10.0)
                    .foregroundColor(Color.black)
                    .padding(5.0)
                    .background(Color.red)
                    .clipShape(Circle())
                    .padding()
                    .onTapGesture { isPresented.toggle() }
                
                , alignment: .topLeading)
            
            .overlay(Text("Custom PopUp View!"))
            .transition(AnyTransition.scale)
            .shadow(radius: 10.0)
        
    }
}


struct CustomPopupView<Content, PopupView>: View where Content: View, PopupView: View {
    
    @Binding var isPresented: Bool
    @ViewBuilder let content: () -> Content
    @ViewBuilder let popupView: () -> PopupView
    let backgroundColor: Color
    let animation: Animation?
    
    var body: some View {
        
        content()
            .animation(nil, value: isPresented)
            .overlay(isPresented ? backgroundColor.ignoresSafeArea() : nil)
            .overlay(isPresented ? popupView() : nil)
            .animation(animation, value: isPresented)
        
    }
}

extension View {
    func customPopupView<PopupView>(isPresented: Binding<Bool>, popupView: @escaping () -> PopupView, backgroundColor: Color = .black.opacity(0.7), animation: Animation? = .default) -> some View where PopupView: View {
        return CustomPopupView(isPresented: isPresented, content: { self }, popupView: popupView, backgroundColor: backgroundColor, animation: animation)
    }
}

enter image description here

This is how I would so it.

struct ContentView: View {
    
    @State var showingShade = false
    
    var body: some View {

        ZStack{
            
            //       Your other views goes here
            
            if showingShade{
                Rectangle()
                    .ignoresSafeArea()
                    .foregroundColor(.black)
                    .opacity(0.5)
            }
        }
    }
}

And then simply set showingShade = true when you want the shade to appear. It might be a good idea to use the same var as your PopUp.

For disabling the view you can use the .disabled() modifier on the specific view you want to disable.

Related