How to dismiss a sheet in SwiftUI

Jan 07, 2024#swiftui

A sheet is a specific type of modal presentation that is commonly used in SwiftUI. It’s a way to present a secondary view in a card-like manner, sliding it up from the bottom of the screen.

Sheets are useful for presenting additional information, settings, or actions without fully obscuring the underlying content. They are typically dismissed by dragging them downwards or by using a dismiss button within the sheet.

To dismiss a sheet programmatically, you can pass an @Binding variable to control the presentation state, or use an environment value of type DismissAction.

Using DismissAction

DismissAction is a new environment value introduced in iOS 15 and macOS 12 that provides an action to dismiss the current presentation. It can be used to dismiss sheets, popovers, navigation views, and windows.

The dismiss environment value can be called as a function because it stores a DismissAction instance, which defines a callAsFunction() method. This method is called implicitly when you write dismiss(), and it performs the appropriate dismissal action depending on the context.

If you use the dismiss environment value inside a sheet, it will dismiss the sheet. If you use it inside a navigation view, it will pop the current view from the navigation stack.

struct ContentView: View {
    @State private var showingSheet = false
    
    var body: some View {
        Button("Show Sheet") {
            showingSheet = true
        }
        .sheet(isPresented: $showingSheet) {
            SheetView()
        }
    }
}

struct SheetView: View {
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        Button("Dismiss Sheet") {
            dismiss()
        }
    }
}

Using @Binding

The @Binding property creates a shared reference, allowing multiple views to access and modify the same value. This creates a two-way connection between the views, allowing the presented view to modify the binding and dismiss itself.

struct ContentView: View {
    @State private var showDetailView = false

    var body: some View {
        Button("Show Detail View") {
            showDetailView = true
        }
        .sheet(isPresented: $showDetailView) {
            DetailView(isPresented: $showDetailView)
        }
    }
}

struct DetailView: View {
    @Binding var isPresented: Bool

    var body: some View {
        Button("Dismiss") {
            isPresented = false // Modifying the binding dismisses the view
        }
    }
}

In this example, we create a @State property named showDetailView to control the presentation of the sheet. The .sheet() modifier uses the isPresented binding to determine whether or not to show the sheet. We pass the isPresented binding to the DetailView using $showDetailView.

Using PresentationMode

Deprecated since iOS 15

PresentationMode is a environment value that indicates whether a view is currently presented by another view, such as a sheet or a navigation view. It also provides a wrappedValue property that can be used to dismiss the view programmatically.

The main difference between PresentationMode and DismissAction is that the former is a binding to the current presentation state, while the latter is a function that performs the dismissal. DismissAction is also more versatile, as it can be used to dismiss sheets, popovers, navigation views, and windows. PresentationMode only works for sheets and navigation views.

struct ContentView: View {
    @State private var isModalPresented = false

    var body: some View {
        Button("Show Modal") {
            isModalPresented = true
        }
        .sheet(isPresented: $isModalPresented) {
            ModalView()
        }
    }
}

struct ModalView: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        VStack {
            Text("This is the modal view!")
            Button("Dismiss") {
                presentationMode.wrappedValue.dismiss()
            }
        }
    }
}