A typed path array ([Product]) can push only one value type. NavigationPath is the type-erased alternative: it stores any mix of Hashable values, and each navigationDestination(for:) resolves its own type. It removes the need for a Route enum.
Mixed Types in One Stack
Append products and strings to the same path; the matching destination modifier handles each.
struct ShopContainerView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(store.products) { product in
NavigationLink(product.title, value: product)
}
.navigationDestination(for: String.self) { query in
SearchView(query: query)
}
.navigationDestination(for: Product.self) { product in
ProductView(product: product)
.toolbar {
Button("Show similar") { path.append(product.query) }
}
}
}
}
}
The Codable Escape Hatch
path.codable returns a NavigationPath.CodableRepresentation you can encode to Data, and an initializer rebuilds the path from it. The gotcha: codable is nil unless every pushed value conforms to Codable, so one non-codable push silently disables persistence.
A NavigationStore for Restoration and Deep Links
Wrap the path in an ObservableObject that owns encoding, decoding, and URL handling.
@MainActor final class NavigationStore: ObservableObject {
@Published var path = NavigationPath()
func handle(_ url: URL) { urlHandler.handle(url, mutating: &path) }
func encoded() -> Data? {
try? path.codable.map(JSONEncoder().encode)
}
func restore(from data: Data) {
do {
let codable = try JSONDecoder().decode(
NavigationPath.CodableRepresentation.self, from: data
)
path = NavigationPath(codable)
} catch {
path = NavigationPath()
}
}
}
The root view restores from @SceneStorage once, then observes the path and writes every change back.
NavigationStack(path: $navigationStore.path) {
ContentView()
.onOpenURL { navigationStore.handle($0) }
.task {
if let navigationData {
navigationStore.restore(from: navigationData)
}
for await _ in navigationStore.$path.values {
navigationData = navigationStore.encoded()
}
}
}