Sheet in SwiftUI
There are two ways to present a sheet in SwiftUI.
1. Binding to a Boolean value
In this way, SwiftUI will present a sheet when a binding to a Boolean value that you provide is true
.
The official method definition is below:
1
2
3
4
5
6
nonisolated
func sheet<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
@ViewBuilder content: @escaping () -> Content
) -> some View where Content : View
Let’s use an example to illustrate how to use it.
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
struct TimelineListView: View {
@EnvironmentObject var timelineStore: TimelineStore
@State private var showingSettings = false
var body: some View {
NavigationView {
List(timelineStore.timelines) { timeline in
TimelineRow(timeline: timeline)
}
.navigationBarTitle("Timelines")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { showingSettings = true }) {
Image(systemName: "gear")
}
}
}
.sheet(isPresented: $showingSettings, onDismiss: didDismiss) {
SettingsView()
}
}
}
private func didDismiss() {
// Handle the dismissing action.
}
}
In the above example, SwiftUI will present SettingsView
when showingSettings
is true
.
2. Using data source item
Another way to present a sheet is using the given item as a data source for the sheet’s content.
The method definition is as follows:
1
2
3
4
5
6
nonisolated
func sheet<Item, Content>(
item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
@ViewBuilder content: @escaping (Item) -> Content
) -> some View where Item : Identifiable, Content : View
A simple example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct TimelineDetailView: View {
let timeline: Timeline
@State private var showingEventToEdit: Event? = nil
var body: some View {
List(timeline.events) { event in
EventRow(event: event)
.contentShape(Rectangle())
.onTapGesture {
showingEventToEdit = event
}
}
.navigationTitle(timeline.title)
.sheet(isPresented: $showingEventToEdit) { event in
EventEditView(timeline: timeline, event: event)
}
}
}
SwiftUI will present a EventEditView
when showingEventToEdit
has value.
Multiple sheets
Sometimes, we may need to present multiple sheets in the same view. How to achieve this in a concise way?
We can create an enum like Sheet
to combine all sheet cases together.
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
34
35
36
37
38
39
40
41
42
43
44
struct TimelineDetailView: View {
enum Sheet: Identifier {
case createEvent
case editEvent(Event)
var id: String {
switch self {
case .createEvent: return "createEvent"
case .editEvent(let event): return "editEvent-\(event.id)"
}
}
}
let timeline: Timeline
@State private var showingSheet: Sheet? = nil
var body: some View {
List(timeline.events) { event in
EventRow(event: event)
.contentShape(Rectangle())
.onTapGesture {
showingSheet = .editEvent(event)
}
}
.navigationTitle(timeline.title)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
showingSheet = .createEvent
} label: {
Image(systemName: "plus")
}
}
}
.sheet(item: $showingSheet) { sheet in
switch sheet {
case .createEvent:
EventCreateView(timeline: timeline)
case .editEvent(let event):
EventEditView(timeline: timeline, event: event)
}
}
}
}
Using this way, we can simply combine all sheets together without multiple state definitions and sheet modifiers.