Skip to content

NavigationStack: The Navigator Pattern (SwiftUI)

A SwiftUI guide for coding agents. Also covers value based navigationlink, navigationdestination placement rules, route enum navigation, centralize navigation logic, data driven navigation ios 16.

The iOS 16 Navigation API is data driven: a NavigationLink carries a Hashable value, and navigationDestination(for:) maps the value's type to a destination view. A Route enum turns that into one type-safe switch for a whole feature.

Bind each link to a value instead of a destination view. One navigationDestination per value type resolves them.

NavigationStack {
  List(products) { product in
    NavigationLink(product.title, value: product)
  }
  .navigationTitle("Products")
  .navigationDestination(for: Product.self) { product in
    ProductDetailView(product: product)
  }
}

The value must conform to Hashable. Stack several navigationDestination modifiers when a screen links to several value types, and use the label-closure initializer of NavigationLink when the link needs custom content.

Placement Rules for navigationDestination

Three rules, and breaking them fails silently:

  1. Place the modifier inside the NavigationStack.
  2. Never place it on a child of a lazy container such as List, ScrollView, or LazyVStack.
  3. When two modifiers handle the same type, the top-level one wins over the lower one.

The Navigator Pattern

Model every route of a feature as one enum, so the whole flow lives in a single switch.

enum Route: Hashable {
  case product(Product)
  case category(Category)
}

Links produce Route values, and the views stay free of destination knowledge.

NavigationLink(value: Route.category(category)) {
  Text(category.query)
}
NavigationLink(value: Route.product(product)) {
  Text(product.title)
}

The container view owns the single navigationDestination and switches over the enum.

struct AppContainerView: View {
  @StateObject private var store = Store()

  var body: some View {
    NavigationStack {
      MasterView(categories: store.categories, recentProducts: store.products)
        .navigationDestination(for: Route.self) { route in
          switch route {
          case let .category(category):
            CategoryView(category: category)
          case let .product(product):
            ProductDetailView(product: product)
          }
        }
    }
  }
}

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-mastering-navigationstack-navigator-pattern" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems