SwiftUI Patterns

Master-Detail with NavigationStack

June 6, 2026
5 min read
Featured image for blog post: Master-Detail with NavigationStack

NavigationStack with value-based navigation is the modern approach to master-detail interfaces in SwiftUI.

Follow along with the code: iOS-Practice on GitHub

Basic Structure

struct MasterDetailExerciseView: View {
    @State private var users: [User] = []
    @State private var selectedUser: User?
    @State private var isLoading = false

    var body: some View {
        List(users, selection: $selectedUser) { user in
            NavigationLink(value: user) {
                UserRow(user: user)
            }
        }
        .navigationTitle("Users")
        .navigationDestination(for: User.self) { user in
            UserDetailView(user: user)
        }
        .task {
            await loadUsers()
        }
    }
}

NavigationLink with Value

NavigationLink(value: user) {
    UserRow(user: user)
}

Instead of specifying a destination view directly, pass a value. The destination is defined elsewhere.

navigationDestination

.navigationDestination(for: User.self) { user in
    UserDetailView(user: user)
}

When any NavigationLink with a User value is tapped, show UserDetailView.

User Row View

struct UserRow: View {
    let user: User

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: user.avatarURL)
                .font(.title)
                .foregroundColor(.accentColor)

            VStack(alignment: .leading) {
                Text(user.name)
                    .font(.headline)
                Text(user.company)
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
        }
    }
}

Detail View with Nested Data

struct UserDetailView: View {
    let user: User
    @State private var posts: [Post] = []
    @State private var isLoading = false

    var body: some View {
        List {
            Section("User Info") {
                HStack(spacing: 16) {
                    Image(systemName: user.avatarURL)
                        .font(.system(size: 50))

                    VStack(alignment: .leading, spacing: 4) {
                        Text(user.name)
                            .font(.title2)
                            .fontWeight(.bold)

                        Text(user.email)
                            .font(.subheadline)
                            .foregroundColor(.secondary)

                        Text(user.company)
                            .font(.caption)
                            .padding(.horizontal, 8)
                            .padding(.vertical, 2)
                            .background(Color.blue.opacity(0.1))
                            .cornerRadius(4)
                    }
                }
            }

            Section("Posts (\(posts.count))") {
                if isLoading {
                    ProgressView()
                } else if posts.isEmpty {
                    ContentUnavailableView("No Posts", systemImage: "doc.text")
                } else {
                    ForEach(posts) { post in
                        PostRow(post: post)
                    }
                }
            }
        }
        .navigationTitle(user.name)
        .navigationBarTitleDisplayMode(.inline)
        .task {
            await loadPosts()
        }
    }

    private func loadPosts() async {
        isLoading = true
        posts = try? await api.fetchPosts(forUserId: user.id) ?? []
        isLoading = false
    }
}

Why Value-Based Navigation?

  1. Type-safe - Compiler checks navigation types
  2. Centralized - All destinations in one place
  3. Deep linking - Easy to restore navigation state
  4. Programmatic - Navigate by setting values

Requirements

Your model must be Hashable:

struct User: Identifiable, Hashable {
    let id: Int
    let name: String
    // ...
}

Interview Tip

NavigationStack replaced NavigationView in iOS 16. Discuss the benefits: type safety, programmatic navigation, and better deep link support.

Originally published on pixelper.com

© 2026 Christopher Moore / Dead Pixel Studio

Let's work together

Professional discovery, design, and complete technical coverage for your ideas

Get in touch