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 index
selectTab(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 stack
push(destination: View): Push a new view onto the stack
pop(last: Int = 1): Pop the last views
popToRoot(): 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 sheet
fullScreenCover(view: View): Present a full-screen cover
popover(view: View): Show a popover
dismissModal(): 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 alert
confirmationDialog(title: String, actions: [Button]): Show a confirmation dialog
openAppSettings(): 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 { ... }
Selecting any option will automatically load the page