How to Prevent Modal/Dialog/Sheet Dismissal using Swipe in SwiftUI

Learn how to stop users from closing modals or dialogs by swiping in SwiftUI with this comprehensive guide

How to Prevent Modal/Dialog/Sheet Dismissal using Swipe in SwiftUI

#Displaying Modals in SwiftUI Using Sheets

To display a dialog or modal in SwiftUI, one common method is to use a sheet. By default, users can close the modal or dialog by simply swiping it down, as shown in the example below.

struct DialogView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non urna ut augue ultricies imperdiet sit amet at dui. Nulla sed egestas metus. In tincidunt maximus pretium")
                Spacer()
                    .navigationTitle("Dialog")
                    .navigationBarTitleDisplayMode(.inline)
            }
            .padding()
        }
    }
}

struct ContentView: View {
    @State private var showDialog: Bool = false
    var body: some View {
        VStack {
            Button("Show Dialog", action: {
                showDialog = true
            })
        }
        .sheet(isPresented: $showDialog){
            DialogView()
        }
        .padding()
    }
}

As seen in the example above, users can close the Dialog/Modal by just swiping it down.

#Preventing Swipe Dismissal of Sheets

To prevent this swipe behavior, it's quite simple. You can achieve it by adding the modifier: interactiveDismissDisabled(_ isDisabled: Bool = true) to the View of the Dialog, as demonstrated below.

struct DialogView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non urna ut augue ultricies imperdiet sit amet at dui. Nulla sed egestas metus. In tincidunt maximus pretium")
                Spacer()
                    .navigationTitle("Dialog")
                    .navigationBarTitleDisplayMode(.inline)
            }
            .padding()
        }
        .interactiveDismissDisabled()
    }
}

By adding the interactiveDismissDisabled modifier, swiping will no longer close the modal or dialog.

Since swiping no longer closes the modal or dialog, it is necessary to add another way to close the modal, usually by using a specific button that dismiss the sheet.

To dismiss the sheet, you can use the dismiss environment variable called by using a button.


struct DialogView: View {
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non urna ut augue ultricies imperdiet sit amet at dui. Nulla sed egestas metus. In tincidunt maximus pretium")
                Spacer()
                    .navigationTitle("Dialog")
                    .navigationBarTitleDisplayMode(.inline)
                    .toolbar {
                        ToolbarItem(placement: .navigationBarTrailing) {
                            Button("Tutup") {
                                dismiss()
                            }
                        }
                    }
            }
            .padding()
        }
        .interactiveDismissDisabled()
    }
}

For further variations, you can enable or disable swipe dismissal based on specific conditions. For example, swipe dismissal disabled during a process and enable it once the process is completed.


struct DialogView: View {
    @Environment(\.dismiss) private var dismiss
    @State private var isLoading: Bool = false
    var body: some View {
        NavigationView {
            VStack {
                Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non urna ut augue ultricies imperdiet sit amet at dui. Nulla sed egestas metus. In tincidunt maximus pretium")
                if isLoading {
                    ProgressView("Sedang memproses...")
                        .padding()
                } else {
                    Button("Proses data"){
                        isLoading = true
                        DispatchQueue.main.asyncAfter(deadline: .now()+3){
                            isLoading = false
                        }
                    }
                    .padding()
                }
                
                Spacer()
                    .navigationTitle("Dialog")
                    .navigationBarTitleDisplayMode(.inline)
                    .toolbar {
                        ToolbarItem(placement: .navigationBarTrailing) {
                            if !isLoading {
                                Button("Tutup") {
                                    dismiss()
                                }
                            }
                        }
                    }
            }
            .padding()
        }
        .interactiveDismissDisabled(isLoading)
    }
}

That concludes this tips and tricks. Enjoy experimenting, and we hope you find this information useful.