Skip to content
All essays
iOSMarch 27, 202514 min

SwiftUI Navigation Complete Guide

Master navigation in SwiftUI apps. NavigationStack, NavigationPath, and custom navigation patterns.

Ü
Ümit Uz
Mobile & Full Stack Developer

Navigation is a crucial part of any iOS app. Learn modern SwiftUI navigation patterns with NavigationStack and NavigationPath.

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

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
}
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
                    }
                }
            }
        }
    }
}

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

  1. 1Use NavigationStack: Modern navigation for iOS 16+
  2. 2Type-safe navigation: Use enums for navigation destinations
  3. 3State management: Centralize navigation state
  4. 4Deep linking: Support URL-based navigation
  5. 5Accessibility: Make navigation accessible
  6. 6Testing: Test navigation flows thoroughly
  7. 7Error handling: Handle invalid navigation states

Navigation is key to great UX. Master SwiftUI navigation patterns!

Related essays

Next essay
Swift Protocols and Extensions: Advanced Patterns