skip to Main Content

I have a custom alert which I am presenting from sheet or from regular view as well. I also have a scrim behind the alert covering entire sheet (which am not sure why it’s not showing up in this sample code) and the sheet also has visible drag indicator (which again is not showing up in sample code).

Anyways, my questions are:

  • How do I disable navigation back button when custom alert is presented?
  • Scrim covers the sheet, but the slightly visible view on top behind the sheet isn’t covered. Is there a way to cover entire height of the screen with scrim?
  • Is there a way to disable drag indicator of the sheet when custom alert is present?

iOS version I am using is 16.4

Code:

import SwiftUI
import Foundation

struct ContentView: View {
    @State private var showDetails = false
    
    var body: some View {
        NavigationStack {
            VStack {
                Button("Show Sheet A") {
                    showDetails = true
                }
            }
            .sheet(isPresented: $showDetails) {
                ContentView1()
            }
        }
    }
}

struct ContentView1: View {
    @State private var showDetails = false
    
    var body: some View {
        NavigationStack {
            VStack {
                Button("Show Sheet B") {
                    showDetails = true
                }
            }
            .navigationDestination(isPresented: $showDetails) {
                ContentView2()
            }
            .presentationDragIndicator(.visible)
        }
    }
}

struct ContentView2: View {
    @State private var showAlert = false
    
    var body: some View {
        VStack {
            Button("Show Alert") {
                showAlert = true
            }
        }
        .navigationTitle("settings")
        .customAlert(isPresented: $showAlert)
        .presentationDragIndicator(.visible)
    }
}

struct CustomAlertView: ViewModifier {
    @Binding var isPresented: Bool

    init(isPresented: Binding<Bool>) {
        self._isPresented = isPresented
    }

    func body(content: Content) -> some View {
        content.animation(nil, value: self.$isPresented.wrappedValue)
            .overlay(self.$isPresented.wrappedValue ? Color.gray.opacity(0.4) : nil) /// Background scrim which not sure why it's not visible.
            .overlay(self.$isPresented.wrappedValue ? alertContent() : nil)
            .animation(.default, value: self.$isPresented.wrappedValue)
    }

    @ViewBuilder
    private func alertContent() -> some View {
        GeometryReader { geometry in
            if self.$isPresented.wrappedValue {
                VStack {
                    Text("title").foregroundColor(Color.black).font(.title2).bold().lineLimit(nil)
                        .padding([.leading, .trailing], 20.0).padding(.top, 16.0)
                    Spacer()
                    
                    Text("message").foregroundColor(Color.secondary).font(.body).bold().lineLimit(nil)
                        .padding([.leading, .trailing], 20.0).padding(.top, 16.0)
                    Spacer()
                    
                    Button { } label: {
                        Text("Click Me")
                    }
                    .frame(width: 250.0, height: 20.0).padding(10.0).background(Color.blue)
                    .foregroundColor(.white).font(.body)
                }.fixedSize(horizontal: false, vertical: true).background(Color.gray).cornerRadius(28)
                .clipped().padding([.leading, .trailing], 5.0)
                .position(x: geometry.size.width/2, y: geometry.size.height/2).frame(width: 328.0)
            }
        }
    }
}

extension View {
    func customAlert(isPresented: Binding<Bool>) -> some View {
        return modifier(CustomAlertView(isPresented: isPresented))
    }
}

enter image description here

I tried setting .presentationBackgroundInteraction(.disabled) on content within custom alert modifier, but that didn’t work either.

2

Answers


  1. You can use your showAlert variable to do that, just add .disabled(showAlert) in your NavigationStack. For extra effect, you can also add .opacity(showAlert ? 0.2 : 1)

    struct ContentView: View {
        @State private var showDetails = false
        
        var body: some View {
            NavigationStack {
                VStack {
                    Button("Show Sheet A") {
                        showDetails = true
                    }
                }
                .sheet(isPresented: $showDetails) {
                    ContentView1()
                }
            }
            .disabled(showAlert)
            .opacity(showAlert ? 0.2 : 1) // change this as you see fit
        }
    }
    
    Login or Signup to reply.
  2. It’s a rather simple change. Just check if the alert is presented, and use the appropriate modifiers to hide the back button + disable the swipe to dismiss gesture.

    struct ContentView2: View {
        @State private var showAlert = false
    
        var body: some View {
            VStack {
                Button("Show Alert") {
                    showAlert = true
                }
            }
            .navigationTitle("settings")
            .customAlert(isPresented: $showAlert)
            .presentationDragIndicator(showAlert ? .hidden : .visible)
            .interactiveDismissDisabled(showAlert)
            .navigationBarBackButtonHidden(showAlert)
        }
    }
    

    To use full screen presentation, you have two options:

    1. Use a navigation stack, which is the preferred method.
    2. If you prefer using sheets, use fullScreenCover.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search