SwiftUI 4: Is it OK to make multiple .navigationDestination() calls at different levels in view hiearchy?
Asked Answered
K

1

6

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).

Kamila answered 13/7, 2022 at 14:22 Comment(4)
Yes, it is very ok :) ... assuming they are all for different types, I think duplication for same types will be ignored.Rostrum
Thanks, @Asperi. Do you have a source or reference to this information, or is it based on your experiments and understanding?Kamila
Its supposed to be ok, but as of beta 3 it can't use the same ID type, hopefully they fix that. Normally these kind of preference modifiers go through a reducer so it's odd this one doesn't.Arris
@Arris Note this sentence in the doc: "You can add more than one navigation destination modifier to the stack if it needs to present more than one kind of data." I think it suggests that the function should be called only once for the same type.Kamila
R
3

It is right in interface contract documentation ("to the stack", ie. entire stack, any place in the stack, except "lazy" as stated below):

demo

Rostrum answered 13/7, 2022 at 14:40 Comment(2)
Thanks. I checked the doc before I posted the question and saw that too. In my opinion the description is ambiguous. But it helps to know how other people interpret it :)Kamila
Everything that is not prohibited explicitly is allowed. And even those one which prohibited there are exceptions. In general - everything in public API is allowed.Rostrum

© 2022 - 2025 — McMap. All rights reserved.