I've been thinking a lot about how navigation and presentation are managed in SwiftUI, and I wanted to propose an idea for a more streamlined approach using environment values. Right now, handling navigation can feel fragmented — especially when juggling default NavigationStack
, modals, and tab selections.
What if SwiftUI provided a set of convenience environment values for these common actions?
Tabs
@Environment(\.selectedTab) var selectedTab
@Environment(\.selectTab) var selectTab
selectedTab
: Read the current tab indexselectTab(index: Int)
: Programmatically switch tabs
Stack Navigation
@Environment(\.stackCount) var stackCount
@Environment(\.push) var push
@Environment(\.pop) var pop
@Environment(\.popToRoot) var popToRoot
stackCount
: Read how many views are in the navigation stackpush(destination: View)
: Push a new view onto the stackpop(last: Int = 1)
: Pop the last viewspopToRoot()
: Return to the root view
Modals
@Environment(\.sheet) var sheet
@Environment(\.fullScreenCover) var fullScreenCover
@Environment(\.popover) var popover
@Environment(\.dismissModal) var dismissModal
sheet(view: View)
: Present a sheetfullScreenCover(view: View)
: Present a full-screen coverpopover(view: View)
: Show a popoverdismissModal()
: Dismiss any presented modal
Alerts & Dialogs
@Environment(\.alert) var alert
@Environment(\.confirmationDialog) var confirmationDialog
@Environment(\.openAppSettings) var openAppSettings
alert(title: String, message: String)
: Show an alertconfirmationDialog(title: String, actions: [Button])
: Show a confirmation dialogopenAppSettings()
: Directly open the app’s settings
Why?
- Clean syntax: This keeps navigation code clean and centralized.
- Consistency: Environment values already manage other app-level concerns (color scheme, locale, etc.). Why not navigation too?
- Reusability: This approach is easy to adapt across different view hierarchies.
Example
@main
struct App: App {
var body: some Scene {
WindowGroup {
TabView {
NavigationStack {
ProductList()
}
.tabItem { ... }
NavigationStack {
OrderList()
}
.tabItem { ... }
}
}
}
}
struct ProductList: View {
@Environment(\.push) var push
@State var products: [Product] = []
var body: some View {
List(protucts) { product in
Button {
push(destination: ProductDetails(product: product))
}
} label: {
...
}
}
.task { ... }
}
}
struct ProductDetails: View { ... }