Navigation is a crucial part of any iOS app. Learn modern SwiftUI navigation patterns with NavigationStack and NavigationPath.
Navigation Basics
NavigationStack
swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("Go to Detail") {
DetailView()
}
NavigationLink("Go to Settings") {
SettingsView()
}
}
.navigationTitle("Home")
}
}
}
struct DetailView: View {
var body: some View {
VStack {
Text("Detail View")
.font(.largeTitle)
}
.navigationTitle("Detail")
.navigationBarTitleDisplayMode(.inline)
}
}Data-Driven Navigation
NavigationPath
swift
struct ContentView: View {
@State private var path: [Screen] = []
var body: some View {
NavigationStack(path: $path) {
List {
Button("Push Detail") {
path.append(.detail)
}
Button("Push Settings") {
path.append(.settings)
}
}
.navigationDestination(for: Screen.self) { screen in
switch screen {
case .detail:
DetailView()
case .settings:
SettingsView()
}
}
.navigationTitle("Home")
}
}
}
enum Screen: Hashable {
case detail
case settings
}Navigation with Values
swift
struct UserList: View {
@State private var path: [User] = []
let users = [
User(id: 1, name: "John"),
User(id: 2, name: "Jane"),
User(id: 3, name: "Bob")
]
var body: some View {
NavigationStack(path: $path) {
List(users) { user in
NavigationLink(value: user) {
Text(user.name)
}
}
.navigationDestination(for: User.self) { user in
UserDetail(user: user)
}
.navigationTitle("Users")
}
}
}
struct User: Identifiable, Hashable {
let id: Int
let name: String
}
struct UserDetail: View {
let user: User
var body: some View {
VStack {
Text(user.name)
.font(.largeTitle)
Text("ID: \(user.id)")
.foregroundColor(.secondary)
}
.navigationTitle("User Detail")
}
}Sheet Presentation
Basic Sheet
swift
struct ContentView: View {
@State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet = true
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}
struct SheetView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack {
Text("Sheet Content")
.font(.title)
Button("Dismiss") {
dismiss()
}
}
.padding()
}
}Sheet with Values
swift
struct ContentView: View {
@State private var selectedUser: User?
var body: some View {
List(users) { user in
Button(user.name) {
selectedUser = user
}
}
.sheet(item: $selectedUser) { user in
UserDetail(user: user)
}
}
}Full Screen Cover
swift
struct ContentView: View {
@State private var showingCover = false
var body: some View {
Button("Show Full Screen") {
showingCover = true
}
.fullScreenCover(isPresented: $showingCover) {
FullScreenView()
}
}
}
struct FullScreenView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
ZStack {
Color.blue.opacity(0.2)
.ignoresSafeArea()
VStack {
Text("Full Screen Content")
.font(.largeTitle)
Button("Close") {
dismiss()
}
.buttonStyle(.borderedProminent)
}
}
}
}Tab Bar Navigation
TabView
swift
struct MainTabView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house.fill")
}
SearchView()
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("Profile", systemImage: "person.fill")
}
}
.tint(.blue)
}
}
struct HomeView: View {
var body: some View {
NavigationStack {
Text("Home Content")
.navigationTitle("Home")
}
}
}Custom Navigation Bar
swift
struct CustomNavBar: View {
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 20) {
ForEach(0..<20) { i in
Text("Item \(i)")
.frame(maxWidth: .infinity)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
}
}
.padding()
}
.navigationTitle("Custom Nav")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Menu") {
// Menu action
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Share") {
// Share action
}
}
}
}
}
}Navigation State Management
Centralized Navigation
swift
@Observable
class NavigationManager {
var path: [AppDestination] = []
func navigate(to destination: AppDestination) {
path.append(destination)
}
func goBack() {
if !path.isEmpty {
path.removeLast()
}
}
func goToRoot() {
path.removeAll()
}
}
enum AppDestination: Hashable {
case home
case profile(id: Int)
case settings
case details(id: Int)
}
struct AppRoot: View {
@State private var navigation = NavigationManager()
var body: some View {
NavigationStack(path: $navigation.path) {
HomeView()
.navigationDestination(for: AppDestination.self) { destination in
switch destination {
case .home:
HomeView()
case .profile(let id):
ProfileView(userId: id)
case .settings:
SettingsView()
case .details(let id):
DetailsView(itemId: id)
}
}
}
.environment(navigation)
}
}
struct HomeView: View {
@Environment(NavigationManager.self) private var navigation
var body: some View {
VStack {
Button("Go to Profile") {
navigation.navigate(to: .profile(id: 123))
}
Button("Go to Settings") {
navigation.navigate(to: .settings)
}
}
.navigationTitle("Home")
}
}Deep Linking
URL Navigation
swift
struct DeepLinkHandler {
func handle(url: URL, navigation: NavigationManager) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let host = components.host else {
return
}
switch host {
case "profile":
if let idString = components.queryItems?.first(where: { $0.name == "id" })?.value,
let id = Int(idString) {
navigation.path = [.profile(id: id)]
}
case "settings":
navigation.path = [.settings]
default:
break
}
}
}
struct AppRoot: View {
@State private var navigation = NavigationManager()
var body: some View {
NavigationStack(path: $navigation.path) {
HomeView()
.onOpenURL { url in
DeepLinkHandler().handle(url: url, navigation: navigation)
}
}
}
}Best Practices
- 1Use NavigationStack: Modern navigation for iOS 16+
- 2Type-safe navigation: Use enums for navigation destinations
- 3State management: Centralize navigation state
- 4Deep linking: Support URL-based navigation
- 5Accessibility: Make navigation accessible
- 6Testing: Test navigation flows thoroughly
- 7Error handling: Handle invalid navigation states
Navigation is key to great UX. Master SwiftUI navigation patterns!