SwiftUI has a less clumsy mechanism for dismissing presented views in iOS 15.
Presentation Mode (iOS 13/14)
Before iOS 15, if you wanted to programmatically dismiss a presented view you used the presentationMode
environment property. For example, here’s a view where I present a modal sheet from the button action (some details omitted):
struct ItemView: View {
@EnvironmentObject var store: ItemStore
@State private var isShowingCart = false
var body: some View {
VStack {
...
Button(action: addToCart) {
Text("Add To Cart")
}
}
.sheet(isPresented: $isShowingCart) {
CartView()
}
}
}
Note: To show the view full screen, replace the .sheet
modifier with .fullScreenCover
:
.fullScreenCover(isPresented: $isShowingCart) {
SwiftUI shows the sheet when we set the state variable isShowingCart
to true
in the addToCart
method:
private func addToCart() {
store.addToCart(item)
isShowingCart = true
}
This presents a modal sheet which you can dismiss by dragging down on the sheet. If you want to dismiss the sheet programmatically you get the presentation mode from the environment:
@Environment(\.presentationMode) var presentationMode
Then you call the dismiss()
method on the wrapped value:
presentationMode.wrappedValue.dismiss()
For example, if I have cancel and save buttons on my presented cart view:
struct CartView: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
List { ... }
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Save") {
store.save()
dismiss()
}
}
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", role: .cancel) {
dismiss()
}
}
}
}
}
private func dismiss() {
presentationMode.wrappedValue.dismiss()
}
}
That always seemed a clumsy mechanism to me so I’m happy we have something better in iOS 15.
Dismiss Action (iOS 15)
In iOS 15, Apple deprecated the presentationMode
environment property, replacing it with a dismiss action. You get the dismiss action from the environment of the presented view:
@Environment(\.dismiss) private var dismiss
You then call the dismiss action directly with no need to mess with wrapped values:
dismiss()
For example, my cart view updated for iOS 15:
struct CartView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
List { ... }
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Checkout") {
store.save()
dismiss()
}
}
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", role: .cancel) {
dismiss()
}
}
}
}
}
}
Note: Calling dismiss() on a view that’s not presented has no effect.
If you want to check if you’re presenting a view use the isPresented
environment property in the view:
@Environment(\.isPresented) private var isPresented
This replaces the property of the same name from the now deprecated presentationMode
. For example, to do an action when a view is first presented:
.onChange(of: isPresented) { isPresented in
if isPresented {
store.prepareCart()
}
}