Navigation & Passing Data
NavigationStack: the modern navigation
To move between screens, wrap your content in a NavigationStack (iOS 16+, the replacement for the older NavigationView). It manages a stack of screens with an automatic back button and title bar.
NavigationStack {
List(users) { user in
NavigationLink(user.name) {
ProfileView(user: user) // the destination screen
}
}
.navigationTitle("Users")
}
A NavigationLink shows a tappable row and pushes its destination when tapped — passing the data (user) is as simple as using it in the destination.
Programmatic navigation
Sometimes you navigate from code (after a login succeeds, say) rather than a tap. You drive this with a path you control:
struct AppView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Button("Go to settings") {
path.append("settings") // push programmatically
}
.navigationDestination(for: String.self) { route in
if route == "settings" { SettingsView() }
}
}
}
}
Value-based navigation
The modern style attaches a value to a link and declares how to render each type of value with navigationDestination(for:). This decouples the link from the destination and scales well in larger apps.
List(users) { user in
NavigationLink(user.name, value: user)
}
.navigationDestination(for: User.self) { user in
ProfileView(user: user)
}
Sheets and full-screen covers
Not every screen should push. Modal presentations (a compose form, a login) slide up over the current screen:
@State private var showSheet = false
Button("Compose") { showSheet = true }
.sheet(isPresented: $showSheet) {
ComposeView()
}
Common mistakes
- Forgetting the
NavigationStackwrapper (links do nothing). - Still using the deprecated
NavigationViewfor new apps. - Putting a navigation title on the stack instead of on the content inside it.
Summary: Wrap screens in aNavigationStack, push withNavigationLink(passing data by simply using it in the destination), navigate from code with aNavigationPath, and use.sheetfor modal screens.