Creating Multiple Sheets in SwiftUI
Learn how to display multiple dialogs and modals in SwiftUI using boolean bindings or Identifiable items
Introduction to Sheets in SwiftUI
To display modals or dialogs in SwiftUI, developers typically rely on the built-in .sheet() modifier. A sheet presents a new view over your existing content, allowing the user to focus on a distinct task or view additional information without leaving the current context.
Usually, calling a single sheet is straightforward. However, modern application flows often demand the ability to present multiple different sheets from the same main view. SwiftUI gives you two primary ways to handle this: using a generic Boolean binding or using an Identifiable item binding.
// Boolean approach
func sheet<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: @escaping () -> Content
) -> some View where Content : View// Identifiable Item approach
func sheet<Item, Content>(
item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
content: @escaping (Item) -> Content
) -> some View where Item : Identifiable, Content : ViewMethod 1: Using Multiple Boolean Bindings
The most obvious way to display multiple modals or dialogs is by attaching multiple .sheet modifiers, each bound to its own boolean state variable. Whenever the state becomes true, the respective sheet appears.
Here is how you can implement two distinctive buttons opening two separate modals:
import SwiftUI
struct ContentView: View {
@State private var showModal1: Bool = false
@State private var showModal2: Bool = false
var body: some View {
VStack(spacing: 20) {
Button("Show Modal 1") {
showModal1 = true
}
.buttonStyle(.borderedProminent)
Button("Show Modal 2") {
showModal2 = true
}
.buttonStyle(.bordered)
}
.padding()
// First Sheet
.sheet(isPresented: $showModal1) {
Text("Content for Modal 1")
.font(.title)
}
// Second Sheet
.sheet(isPresented: $showModal2) {
Text("Content for Modal 2")
.font(.title)
}
}
}
This method is incredibly simple and ideal if your view only needs to manage a small number of sheets (typically one or two). However, as the number of modals increases, your code quickly becomes cluttered with repetitive @State boolean variables and stacked .sheet modifiers. This is often an anti-pattern when it comes to scalability scalability.
Method 2: Using an Identifiable Binding (Recommended)
A much cleaner and scalable alternative is by passing an Identifiable binding into a single .sheet modifier. Instead of observing multiple true/false states, SwiftUI tracks the presence of an object. When the object is assigned a value, the sheet opens; when it is set back to nil, the sheet closes.
To substitute multiple boolean flags, you can establish an enum that conforms to the Identifiable protocol. By utilizing the built-in hashValue to supply an ID, your setup becomes extremely robust.
enum ActiveSheet: Identifiable {
case profile, settings, help
var id: Int {
hashValue
}
}
// Only one state variable to handle all scenarios
@State private var activeSheet: ActiveSheet?With this structure in place, the declarative UI code becomes highly optimized:
struct ContentView: View {
@State private var activeSheet: ActiveSheet?
var body: some View {
VStack(spacing: 20) {
Button("Open Profile") {
activeSheet = .profile
}
Button("Open Settings") {
activeSheet = .settings
}
Button("Get Help") {
activeSheet = .help
}
}
.padding()
// Single sheet modifier
.sheet(item: $activeSheet) { item in
switch item {
case .profile:
Text("User Profile View")
case .settings:
Text("App Settings View")
case .help:
Text("Help & Support View")
}
}
}
}
One crucial rule to remember: inside the closure where you switch on the item, the switch cases must be exhaustive. SwiftUI demands that every possible case return a valid View. If an enum case gets overlooked, Xcode will immediately halt compilation with an exhaustiveness error.
Which Approach Should You Choose?
• Boolean Bindings are ideal for quick layouts where only one or two simple sheets interact with the interface. They are effortless to implement for a single modal.
• Identifiable Bindings are the industry-standard approach for more complex screens. Using a routing enum dramatically cleans up the View body, prevents potential conflicting modal activations, and makes passing necessary structural data into the newly spawned sheets much easier.
Conclusion
Mastering navigation flows within SwiftUI takes a bit of practice. Transitioning from boolean toggles to leveraging the Identifiable protocol is a milestone for every iOS developer seeking to write cleaner, more maintainable code.
Good luck experimenting with different modal presentations in your future projects. Happy coding!
