A sheet in iOS is a system provided view that appears modally on top of any other view that's currently displayed. A sheet is by default empty, and it's our job to provide its content. It's probably the best way to show additional information to users, ask for their input, or let them interact with the app without leaving entirely what they have been currently doing. Presenting sheets in SwiftUI is a fast and a no-brainer task, so read on to find out the few how-to guidelines that govern it.
Presenting a sheet
A sheet in SwiftUI is available as a view modifier, which is usually applied to the outermost view object. Whether the sheet is presented or not is something we usually control with a boolean @State property, declared locally in the view's structure. Initially we set it to false, indicating that the sheet is hidden. In order to make the sheet appear, we simply change its value to true when necessary.
A sheet view modifier expects two arguments; the first is the binding value of the property that controls its presented state. The second is a closure, where we provide the view that will be displayed as the sheet's content.
To get a taste of all that, here's the following simple example where a button's tap triggers the presentation of a sheet:
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Button("Present Sheet") {
// By setting true to showSheet the
// sheet will be presented.
showSheet = true
}.buttonConfiguration()
.sheet(isPresented: $showSheet, content: {
Text("Hello World from Sheet!")
})
}
}
See that the default value of showSheet is false, and we change that in the button's action closure. Also notice the sheet modifier; it's applied to the button in this example, as that's the only view here. The $showSheet binding value determines the sheet's presented state, and the content closure is the place that contains the sheet's content. In this case it's just a simple text.
The above simple implementation results to this:

Dismissing a sheet
There are three ways to dismiss a sheet. The first and simplest one is provided by iOS, and it requires no effort at all on our part. All we have to do is to drag it towards bottom, and the sheet will go away.
The second approach is mostly convenient when we implement sheet's content right in the content closure. There, we simply change the showSheet value to false, and the sheet is dismissed.
In the following example, there is a button along with the text in the sheet. By tapping on it, the showSheet property becomes false:
.sheet(isPresented: $showSheet, content: {
Text("Hello World from Sheet!")
Button("Dismiss") {
showSheet = false
}
})
The third method to dismiss a sheet becomes handy when the sheet content is implemented in a different view. Suppose that we have the following:
struct SheetContentView: View {
var body: some View {
Text("Hello World from Sheet!")
Spacer().frame(height: 50)
Button("Dismiss") {
}
}
}
To show this view, we just need to create a SheetContentView instance in the sheet's content closure:
.sheet(isPresented: $showSheet, content: {
SheetContentView()
})
Right now there is no way to dismiss the sheet, other than dragging it downwards. To make it disappear when tapping on the dismiss button above, it's necessary to access the presentationMode value from SwiftUI's environment using the @Environment property wrapper:
struct SheetContentView: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
...
}
}
Then, we can dismiss the sheet by calling the dismiss() method through the wrappedValue of the presentationMode property:
Button("Dismiss") {
presentationMode.wrappedValue.dismiss()
}
Alternatively, we can dismiss the sheet by passing the binding value of the showSheet property to the sheet's content view. To do that, we must declare a property marked with the @Binding property wrapper in that view first:
struct SheetContentView: View {
@Binding var showSheet: Bool
var body: some View {
...
}
}
Next, we set false to showSheet in the button that dismisses the sheet:
Button("Dismiss") {
showSheet = false
}
Lastly, we initialize a SheetContentView instance passing the binding value of the showSheet state property:
.sheet(isPresented: $showSheet, content: {
SheetContentView(showSheet: $showSheet)
})
The last approach demonstrated right above is the one that requires the most effort, so unless you have a specific reason to use it, you'd better stick to the presentationMode property from the SwiftUI views' environment. It's faster and easier.

Sheet with full screen cover
Most of the times, the default sheet's behavior that allows to dismiss it by dragging downwards is a convenient feature. However, there might be cases where it's better to avoid that, and dismiss the sheet programmatically.
In order to manage that, it's necessary to present the sheet in a way so it covers the full screen. Thankfully, that's pretty easy to do; instead of using the sheet(isPresented:content:) view modifier, simply use the fullScreenCover(isPresented:content:) modifier:
struct ContentView: View {
@State private var showSheet = false
var body: some View {
...
.fullScreenCover(isPresented: $showSheet, content: {
SheetContentView()
})
}
}
One sheet for many occasions
There are cases where we want to present a sheet for more than one purposes and with different content. In such circumstances, declaring multiple sheet view modifiers in order to cover all cases is possible, but not recommended; we'd also need an equal number of state properties to manage the presented state of the sheets.
The best solution is to have one sheet view modifier only, and present the proper content conditionally in the sheet's content closure. That way we have one state property to deal with only.
Regarding the conditional presentation of the sheet's content, we can either use boolean properties, or follow another approach, which is my favorite one; to declare a custom enum with cases matching to each sheet content.
The following example demonstrates how to do that:
struct ContentView: View {
enum SheetContent {
case first, second, third
}
@State private var sheetContent: SheetContent = .first
@State private var showSheet = false
var body: some View {
Button("Show first") {
sheetContent = .first
showSheet = true
}
Button("Show second") {
sheetContent = .second
showSheet = true
}
Button("Show third") {
sheetContent = .third
showSheet = true
}
.sheet(isPresented: $showSheet, content: {
switch sheetContent {
case .first: FirstSheetContentView()
case .second: SecondSheetContentView()
case .third: ThirdSheetContentView()
}
})
}
}
SheetContent is a custom type with cases representing the content that the sheet should present, and sheetContent property is of that type. Depending on which one of the three buttons is tapped, sheetContent gets the proper value that indicates the view that should be displayed when the sheet will appear.
In the sheet's content closure, there is a switch statement that checks the value of the sheetContent property, and depending on the case, it initializes the proper view.
Performing actions on sheet's dismiss event
In certain cases you may need to perform additional actions right after the sheet has been dismissed. To manage that, it's necessary to use another initializer for the sheet view modifier, called sheet(isPresented:onDismiss:content:).
The onDismiss parameter is a closure that gets called right upon the sheet's dismissal. That's the place to add any tasks you want to perform once that event has occurred.
The following example prints a message right when the sheet is dismissed:
.sheet(isPresented: $showSheet, onDismiss: {
print("Goodbye sheet!")
}, content: {
SheetContentView()
})
Summary
As you see, it's easy to present sheets in SwiftUI. And in this post, I shared with you pretty much everything you need to know about them; how to present, dismiss, and provide content to a sheet, as well as a few useful tips that will make things easier for you. Modal sheets can be proved a handy tool when building apps, so why not using them when that seems appropriate? Thank you for reading!