In this article we’ll learn about Sheets
in SwiftUI. We will explain when, and how, to use them, we’ll explain how they’re created and shown. We’ll also look at a few examples of their usage. Let’s get started!
Table of Contents
What are SwiftUI Sheets and when should we use them?
In SwiftUI, Sheets
are views that are modally shown on top of the current content on the screen, without the need to navigate to a new page. They are useful for showing content that is contextually relevant to what the user is currently seeing.
“When should we use them?” is a great question that we’ll answer with our own view of it. When the content we want to show is too complex for a simple Alert
, and not complex enough to show an entire screen or flow, that’s a great place to use contextual Sheets. Examples of that would be product detail pages, views to add items to collections, screens to edit the details of items, and so on.
How to use Sheets
Now let’s get our hands into the dough and look at how Sheets
work from a technical perspective.
Showing and hiding a SwiftUI Sheet
Let’s start by looking at how a SwiftUI Sheet
is shown. This is simple, since showing a Sheet
is just a matter of adding a modifier to our current View:
.sheet(isPresented: $myBooleanCondition) {
ViewToShow()
}
That is the simplest possible way to show a Sheet
. isPresented
takes a Boolean
and shows the Sheet
when the condition is true.
Now let’s have a more complete example that is made of a Button
that shows a Sheet
which is dismissible:
struct ContentView: View {
@State private var isShown: Bool = false
var body: some View {
Button {
isShown = true
} label: {
Text("Show the Sheet")
}.sheet(isPresented: $isShown){
MySheet()
}
}
}
struct MySheet: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button {
dismiss()
} label: {
Text("Dismiss the Sheet")
}
}
}
Our ContentView
is quite straightforward. We have a Bool isShown
, that is then observed by the Sheet
to know when to show our MySheet
modal, and that Bool
is toggled to true
with the click of a button.
Now our MySheet
introduces something new with @Environment(\.dismiss) var dismiss
. What this single line does, is give us access to the dismiss environmental value, which then tells our SwiftUI
framework to dismiss the current View
. When the framework does that, the Sheet being presented knows it needs to automatically toggle the isShown
to false, and that’s why it gets dismissed and can be shown again.
SwiftUI Sheet Properties
Now that we’ve looked at basic showing and dismissing of Sheets
, let us dive into the properties that we can change to better customise our Sheets, and the experience they provide. They are not massively configurable, since a Sheet is just the way we present a SwiftUI View
, however there’s a couple things we can change:
Enabling/Disabling drag to dismiss
If you try out the previous Sheet
, you’ll see that you can dismiss the modal by dragging it downwards, which is the default behaviour. To change this we need to modify our shown View
to add interactiveDismissDisabled()
to it. So, the example Sheet
we used previously would now be:
struct MySheet: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button {
dismiss()
} label: {
Text("Dismiss the Sheet")
}
.interactiveDismissDisabled()
}
}
Full Screen covering
If you’d like your sheets to cover the entirety of the screen, instead of just the majority of it, becoming a modal view, you would have to show them using fullScreenCover
. Which means that the equivalent of our:
.sheet(isPresented: $isShown){
MySheet()
}
would be:
.fullScreenCover(isPresented: $isShown){
MySheet()
}
With this change, you can start presenting sheets that become a modal sheet because they have a screen cover that prevents the user from interacting with any other child view.
Showing Several Sheets on the same view
It’s common to have support for multiple different Sheets
on the same screen. There’s two main approaches to doing this. One is by having multiple Sheet
entries, and the other is by having a single Sheet
entry, but with the ability to identify them. We will now look into how those approaches can be used in practice.
Multiple Sheet entries
As the name implies, this method is used by having several Sheet
modifiers on our Views
. Each modifier should have their own boolean checks. Let’s have a look at a View
with four different Sheet
modifiers and four different Views
that can be modally shown:
struct ContentView: View {
@State private var isShowingSheet1: Bool = false
@State private var isShowingSheet2: Bool = false
@State private var isShowingSheet3: Bool = false
@State private var isShowingSheet4: Bool = false
var body: some View {
Button {
isShowingSheet1 = true
} label: {
Text("Show Sheet 1")
}
Button {
isShowingSheet2 = true
} label: {
Text("Show Sheet 2")
}
Button {
isShowingSheet3 = true
} label: {
Text("Show Sheet 3")
}
Button {
isShowingSheet4 = true
} label: {
Text("Show Sheet 4")
}
.sheet(isPresented: $isShowingSheet1) {
MySheet()
}.sheet(isPresented: $isShowingSheet2) {
MySecondSheet()
}.sheet(isPresented: $isShowingSheet3) {
MyThirdSheet()
}.sheet(isPresented: $isShowingSheet4) {
MyFourthSheet()
}
}
}
struct MySheet: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("I am Sheet 1")
Button {
dismiss()
} label: {
Text("Dismiss the Sheet")
}
}
}
struct MySecondSheet: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("I am Sheet 2")
Button {
dismiss()
} label: {
Text("Dismiss the Sheet")
}
}
}
struct MyThirdSheet: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("I am Sheet 3")
Button {
dismiss()
} label: {
Text("Dismiss the Sheet")
}
}
}
struct MyFourthSheet: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("I am Sheet 4")
Button {
dismiss()
} label: {
Text("Dismiss the Sheet")
}
}
}
In this example we’ll have four buttons in our main View
, and depending on which you choose, the respective Sheet
is shown. While it does work, for screens with multiple Sheets
this becomes quite wordy. We can solve that by re-writing our Sheet
modifiers into a single one.
Having multiple Sheet support with a Single Sheet entry
This is usually achieved by using Enums
. If you’d like to learn more about Enums
, we have an article covering them, here. With the help of Enums
we’ll declare a group of values that cover all our Sheets
:
enum SheetType: String, Identifiable {
var id: String { rawValue }
case sheet1, sheet2, sheet3, sheet4
}
With that we can now add a property to our View
that is of SheetType
@State private var shownSheet: SheetType?
To show this particular Sheet
we will use a different Sheet
initialisation method, instead of a boolean that is observed to see when the Sheet
should be shown. This initialiser takes an optional property and when there’s a value it is used to show the Sheet
. It is easier seen than described, so:
.sheet(item: $shownSheet) { sheet in
switch sheet {
case .sheet1: MySheet()
case .sheet2: MySecondSheet()
case .sheet3: MyThirdSheet()
case .sheet4: MyFourthSheet()
}
}
Whenever shownSheet
is not nil
, the block gets called with the most up-to-date value in sheet, and we use that to show the appropriate screen.
With all that said and done, our entire View
now looks like this:
struct ContentView: View {
enum SheetType: String, Identifiable {
var id: String { rawValue }
case sheet1, sheet2, sheet3, sheet4
}
@State private var shownSheet: SheetType?
var body: some View {
Button {
shownSheet = .sheet1
} label: {
Text("Show Sheet 1")
}
Button {
shownSheet = .sheet2
} label: {
Text("Show Sheet 2")
}
Button {
shownSheet = .sheet3
} label: {
Text("Show Sheet 3")
}
Button {
shownSheet = .sheet4
} label: {
Text("Show Sheet 4")
}
.sheet(item: $shownSheet) { sheet in
switch sheet {
case .sheet1:
MySheet()
case .sheet2: MySecondSheet()
case .sheet3: MyThirdSheet()
case .sheet4: MyFourthSheet()
}
}
}
}
To Sum up
SwiftUI Sheets are a great way to show our Views
modally. This helps to prevent the user from losing the context of what they were doing.
We can show truly modal sheets that do not take the entire screen by using the .sheet
modifier, and full screen sheets by using the .fullScreenCover
modifier.
The majority of times we can simply control their visibility by using a Boolean
to toggle if it is being presented or not; we also have a secondary way of controlling that with an item-based initialiser.
We hope this article was helpful in giving you an overview of what Sheets
are, and how you can use them in your own apps.