A sheet in iOS is a system provided view that appears modally on top of any other currently displayed view. A sheet is by default empty, and it’s our job as developers to provide it with custom views and content. It constitutes a good solution in order to show additional information to users, ask for their input, or let them perform an operation 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 way that the sheet must be hidden. In order to make the sheet appear, we change its value to true when necessary or after a user’s interaction.
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 everything just described, consider the following simple example where a button’s tap triggers the presentation of a sheet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 see how we integrate the sheet; it’s a view modifier 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 whatever must be displayed to the user. In this case is 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 requires no effort at all from the developer. 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 its content
closure. There, we simply change the showSheet
value to false, and the sheet will be 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:
1 2 3 4 5 6 7 8 9 |
.sheet(isPresented: $showSheet, content: { Text(“Hello World from Sheet!”) Button(“Dismiss”) { showSheet = false } }) |
The third method in order to dismiss a sheet becomes handy when the sheet content is implemented in a different view structure. Suppose that we have the following view:
1 2 3 4 5 6 7 8 9 10 11 12 |
struct SheetContentView: View { var body: some View { Text(“Hello World from Sheet!”) Spacer().frame(height: 50) Button(“Dismiss”) { } } } |
To use it, we just need to create a SheetContentView
instance in the sheet’s content
closure:
1 2 3 4 5 |
.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:
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 4 5 |
Button(“Dismiss”) { presentationMode.wrappedValue.dismiss() } |
Alternatively, we can dismiss the sheet by passing the binding value of the showSheet
property that controls the presented state to the sheet’s content view. To do that, we must declare a property marked with the @Binding property wrapper in that view first:
1 2 3 4 5 6 7 8 9 |
struct SheetContentView: View { @Binding var showSheet: Bool var body: some View { … } } |
Next, set false to showSheet
in the button that dismisses the sheet:
1 2 3 4 5 |
Button(“Dismiss”) { showSheet = false } |
Lastly, initialize a SheetContentView
instance passing the binding value of the showSheet
state property:
1 2 3 4 5 |
.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 more suitable to avoid that, and dismiss the sheet programmatically after some user’s input or interaction.
In order to manage that, it’s needed to present the sheet 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct ContentView: View { @State private var showSheet = false var body: some View { … .fullScreenCover(isPresented: $showSheet, content: { SheetContentView() }) } } |
One sheet for many occasions
Sometimes there might be occasions where you will want to present a sheet for more than one purposes, and obviously with different content. In such circumstances, declaring multiple sheet view modifiers in order to cover all cases is not recommended; you will 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 you will have one state property to control the sheets appearance, and a more elegant solution as you are going to see right next.
Regarding the conditional presentation of sheet content, you can use either boolean properties, or follow another approach, which is my favorite one; to declare a custom enumeration with cases matching to each occasion that sheet should be presented for.
The following example demonstrates how to do that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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 contain, 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:
1 2 3 4 5 6 7 |
.sheet(isPresented: $showSheet, onDismiss: { print(“Goodbye sheet!”) }, content: { SheetContentView() }) |
Summary
As you can see, presenting sheets in SwiftUI is not a difficult task. 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!