How to avoid rebuild view when tap on different button on NavigationSplitView

487 Views Asked by At

I have tried apple example Bringing robust navigation structure to your SwiftUI app

so my code looks like this

        NavigationSplitView(
            columnVisibility: $navigationModel.columnVisibility
        ) {
            List(
                categories,
                selection: $navigationModel.selectedCategory
            ) { category in
                NavigationLink(category.localizedName, value: category)
            }
            .navigationTitle("Categories")
            .toolbar {
                ExperienceButton(isActive: $showExperiencePicker)
            }
        } detail: {
            NavigationStack(path: $navigationModel.recipePath) {
                RecipeGrid(category: navigationModel.selectedCategory)
            }
        }

Details View

struct RecipeGrid: View {
    var category: Category?
    var dataModel = DataModel.shared

    var body: some View {
        ZStack {
            if let category = category {
                ScrollView {
                    LazyVGrid(columns: columns) {
                        ForEach(dataModel.recipes(in: category)) { recipe in
                            NavigationLink(value: recipe) {
                                RecipeTile(recipe: recipe)
                            }
                            .buttonStyle(.plain)
                        }
                    }
                    .padding()
                }
                .navigationTitle(category.localizedName)
                .navigationDestination(for: Recipe.self) { recipe in
                    RecipeDetail(recipe: recipe) { relatedRecipe in
                        NavigationLink(value: relatedRecipe) {
                            RecipeTile(recipe: relatedRecipe)
                        }
                        .buttonStyle(.plain)
                    }
                }
            } else {
                Text("Choose a category")
                    .navigationTitle("")
            }
        }
    }

    var columns: [GridItem] {
        [ GridItem(.adaptive(minimum: 240)) ]
    }
}

My issue is if I go to details view then tap on other sidebar item after that return to same tap, it will return to rootview also onAppear it toggled! that mean the view did rebuild itself

enter image description here

on Apple News app it will stay on save view it won't rebuild or return to rootview when I change sidebar item

enter image description here

I want same behavior, but I don't know how can I do it

I didn't find any question here or any article explain how to do the same behavior as Apple News app

2

There are 2 best solutions below

1
On

I think the solution is rather complex. I tried modifying the code myself but there’s quite a few things that need to change.

Firstly, the problem is caused because here NavigationStack(path: $navigationModel.recipePath) the recipePath gets reset every time the users taps the sidebar. I wish I understood why, but I passed to path a manual binding Binding.init(get:set:).

Secondly, the route is [Recipe], so when you select another category it makes sense that this route will be emptied. Otherwise, when the user taps the sidebar, the selected item from the previous category would still show.

So you would need the route to be a dictionary ([Category: [Recipe]]) and you need to hold the route for each category.

After I made this change, I saw in Apple Documentation that you can restore the route after the view has appeared, probably this solves the first problem.

So every time the category would change, I would make a copy of the route for that category. Then I tried to restore it when the category was tapped again, but I got stuck here because onAppear wasn’t triggered for me on RecipeGrid(…).onAppear {}.

0
On

I finally solved the problem using ZStack. The root cause of the view being rebuilt was the change in $navigationModel.recipePath, which triggered the reconstruction of the detail part. So I wondered if it was possible to prevent the view from being rebuilt by only changing the .opacity of different views in the detail view, assuming the view tree structure remains unchanged. And indeed, it worked with my code. Switching elements in the SideBar no longer causes the view to rebuild.

You might try implementing your view with this kind of pseudocode:

NavigationSplitView {
    // sidebar
} detail: {
    ZStack {
        RecipeGridA
            .opacity(navigationModel.selectedCategory == .categoryA ? 1 : 0)
        RecipeGridB
            .opacity(navigationModel.selectedCategory == .categoryB ? 1 : 0)
        RecipeGridC
            .opacity(navigationModel.selectedCategory == .categoryC ? 1 : 0)
    }
}