swiftuitoolbarswiftui-navigationstack

How do I maintain a consistent toolbar across all child screens in a navigation stack?


I have added a toolbar to the root view of my navigation path and it disappears when I segue to the next screen in my path.

I have tried placing it as a modifier to the NavigationStack but then it disappears completely.

How do I make this such that after navigating to the detail screen, the toolbar still shows my cart toolbar item in view?

I have created a navigation stack like so:

var body: some View {
    NavigationStack(path: $navigation.navigationPath) {
        LandingScreen()
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: {
                        // TODO: Add action here
                    }) {
                        Image(systemName: "cart")
                    }
                }
                ToolbarItem(placement: .principal) {
                    Text("Store") // Locally I add a custom font to this which is why it is done this way.
                }
            }
            .navigationDestination(for: Item.self) { item in
                DetailScreen(item: item)
            }
    }
}

Here is my landing screen:

struct LandingScreen : View {

    @EnvironmentObject var navigation: NavigationManager

    /// Temporary data for this screen until it is setup
    let items = [
        Item(name: "Item 1"),
        Item(name: "Item 2"),
        Item(name: "Item 3"),
        Item(name: "Item 4"),
        Item(name: "Item 5"),
        Item(name: "Item 6"),
        Item(name: "Item 7"),
        Item(name: "Item 8"),
        Item(name: "Item 9"),
        Item(name: "Item 10"),
        Item(name: "Item 11"),
        Item(name: "Item 12")
    ]

    var body: some View {
        ScrollView(showsIndicators: false) {
            LazyVStack {
                ForEach(items, id: \.self)
                {
                    item in
                        Button(
                            action: {
                                navigation.navigationPath.append(item)
                            }, 
                            label: {
                                Text("Item Button")
                            })
                }
            }
        }
    }
    .padding([.leading, .trailing])
}

And here is my DetailScreen:

struct DetailScreen : View {

    let item: Item
    var body: some View {
        Text("Detail Screen for \(item.name)")
    }
}

Solution

  • Create a view extension function that will add the toolbar to any view:

    extension View {
        
        func storeToolbar() -> some View {
            self
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button(action: {
                            // TODO: Add action here
                        }) {
                            Image(systemName: "cart")
                        }
                    }
                    
                    ToolbarItem(placement: .principal) {
                        Text("Store")
                    }
                }
        }
    }
    

    Then just add .storeToolbar() to any view you want to display that toolbar on:

    LandingScreen()
        .storeToolbar()