We will discuss about which approach is better for each scenario. Enum-based vs Boolean-based.
We will add some documentation on usage on top of the SwiftUI built-in framework for sheets.
iOS 17.0+
Table of Contents
- The Code
- Usage
- @State vs @Observable
- Adding Navigation to the presented sheets
- Related Articles
- Related Reading
The Code
Let’s start out with the code:
- In the snippet, we are just adding a ViewModifier + a View extension to add a close button modifier to a Navigation Bar. Nothing Fancy.
- What’s interesting about the snippet is the documentation we added on top of the code.
- Explaining when to go for
sheet(isPresented: Boolean)
vssheet(item: Enum)
. - It will serve as the base for sheet manipulation for the whole app.
Usage
Boolean-based
If your screen can only present one type of sheet, then the easiest way is to use sheet(isPresented:)
. We only need to manage one state property, so there is no need for the enum-based approach.
struct SomeScreen: View {
@State private var isPresentingSheet: Bool = false
var body: some View {
Button("Present Sheet") {
isPresentingSheet = true
}
.sheet(isPresented: $isPresentingSheet) {
Text("Presented sheet")
}
}
}
Enum-based
If your screen can present multiple types of sheets, then the best way is to use sheet(item:)
. We need to manage an enum property, so there is no need for the enum-based approach.
struct SomeScreen: View {
enum Sheet: String, Identifiable {
case firstPresentedScreen, secondScreen, thirdScreen
var id: String { rawValue }
}
@State private var presentedSheet: Sheet?
var body: some View {
VStack {
Button("Present Sheet") {
presentedSheet = .firstPresentedScreen
}
Button("Present Second Sheet") {
presentedSheet = .secondScreen
}
Button("Present Third Sheet") {
presentedSheet = .thirdScreen
}
}
.sheet(item: $presentedSheet) { sheet in
switch sheet {
case .firstPresentedScreen:
Text("First presented screen")
case .secondScreen:
Text("Second screen")
case .thirdScreen:
Text("Third screen")
}
}
}
}
Something interesting about the sheet(item:)
approach is that the system will take care of dismissing the current sheet, if by some reason, another sheet is presented using the same property.
Example:
- SomeScreen’s presentedSheet is set to
.firstPresentedScreen
- The FirstPresentedScreen is presented as a sheet
- SomeScreen’s presentedSheet is set to
.secondScreen
- The FirstPresentedScreen is dismissed
- The SecondScreen is presented as a sheet
@State vs @Observable
In most cases, managing the state of the sheets directly on the SwiftUI views as a @State
property should be enough. However, if there is some business logic involved, it might be a good idea to move the state to an @Observable
object. The benefit of the @Observable
object is that it can be shared with other views, and it can be used to trigger some actions when the state changes + we could add some unit tests over the logic.
Adding Navigation to the presented sheets
It’s really straight forward to add an stack-based navigation system to the presented sheets. We can just follow the same approach explained in the NavigationRouter article for the Router of the Sheet-presented flow.
Related Articles
- NavigationRouter
- Simple Modularization Setup
- New App Checklist
- ViewStateController
- Enhancing Testability without Protocols