I wonder if it's OK to call navigationDestination()
multiple times at different levels in the view hierarchy? I googled but all the examples I found on the net called it on the top level view within NavigationStack
. I tried the following code. It worked fine, but I'm not sure if it's by accident or by design. I'd like to code this way because it helps to organize the code.
struct ID<Value>: Hashable, Equatable {
let uuid: UUID
init() {
uuid = UUID()
}
}
typealias CategoryID = ID<Category>
typealias ItemID = ID<Item>
struct Catalog {
var categories: [Category]
func getCategory(_ categoryID: CategoryID) -> Category? {
return categories.first { $0.id == categoryID }
}
func getItem(_ categoryID: CategoryID, _ itemID: ItemID) -> Item? {
guard let category = getCategory(categoryID) else { return nil }
return category.items.first { $0.id == itemID }
}
}
struct Category: Identifiable {
var id: CategoryID = .init()
var name: String
var items: [Item]
}
struct Item: Identifiable {
var id: ItemID = .init()
var name: String
}
func createTestData() -> Catalog {
let categories = [
Category(name: "Category A", items: [Item(name: "Item a1"), Item(name: "Item a2")]),
Category(name: "Category B", items: [Item(name: "Item b1"), Item(name: "Item b2")])
]
return Catalog(categories: categories)
}
struct ContentView: View {
@State var catalog: Catalog = createTestData()
var body: some View {
NavigationStack {
List {
ForEach(catalog.categories) { category in
NavigationLink(category.name, value: category.id)
}
}
// This is the typical place to make one or multiple calls of navigationDestination().
.navigationDestination(for: CategoryID.self) { categoryID in
CategoryView(categoryID: categoryID, catalog: $catalog)
}
}
}
}
struct CategoryView: View {
let categoryID: CategoryID
@Binding var catalog: Catalog
var body: some View {
if let category = catalog.getCategory(categoryID) {
List {
ForEach(category.items) { item in
NavigationLink(item.name, value: item.id)
}
}
// Q: is it OK to call navigationDestination() here?
.navigationDestination(for: ItemID.self) { itemID in
ItemView(categoryID: categoryID, itemID: itemID, catalog: $catalog)
}
}
}
}
struct ItemView: View {
let categoryID: CategoryID
let itemID: ItemID
@Binding var catalog: Catalog
var body: some View {
if let item = catalog.getItem(categoryID, itemID) {
Text(item.name)
}
}
}
Note the code uses a generic identifier type because otherwise SwifUI couldn't differentiate navigationDestination(for: CategoryID)
and navigationDestination(for: ItemID)
.
CodePudding user response:
It is right in interface contract documentation ("to the stack", ie. entire stack, any place in the stack, except "lazy" as stated below):