NavigationStack within NavigationSplitView's detail column clears the path when disappearing

I'd like to persist the path on a sidebar selection, so when user comes back to the sidebar selection, they land where they were before. Unexpectedly, the path gets cleared when sidebarSelection is changed from the NavigationStack that uses the path to something else. Is this an intended behavior? How to workaround it?

Using TabView is one way, but TabView has its own problems, so I'm wondering if there's a solution within NavigationSplitView first.

Here is a minimal reproduce of the issue:

struct Home2: View {
  private enum SidebarSelection: CaseIterable, Identifiable {
    var id: Self { self }
    case files, tags
  }

  @State
  private var sidebarSelection: SidebarSelection? = .files
  @State
  private var path: [Int] = []

  var body: some View {
    NavigationSplitView {
      List(SidebarSelection.allCases, selection: $sidebarSelection) { selection in
        switch selection {
        case .files: Label("Files", image: "custom.square.stack")
        case .tags: Label("Tags", systemImage: "grid")
        }
      }
    } detail: {
      switch sidebarSelection {
      case .files:
        NavigationStack(path: $path) {
          Subview(depth: 1)
            .navigationDestination(for: Int.self) { Subview(depth: $0) }
        }
      case .tags: Text("Tags")
      default: EmptyView()
      }
    }
    .onChange(of: path) { print("\(path.count)") }
  }
}

struct Subview: View {
  let depth: Int

  var body: some View {
    List { NavigationLink("Next: \(depth + 1)", value: depth + 1) }
      .navigationTitle("Depth \(depth)")
  }
}

#Preview("Home2") { Home2() }

@hanstardev You might want to reconsider how your path is modeled and use the SidebarSelection enum to represent the various paths the detailView can navigate to or the NavigationPath to represent the heterogeneous list of data.

For example:

enum SidebarSelection:  Identifiable {
    var id: Int {
        switch self {
        case .files(let number):
            return number
        case .tags:
            return 10000
        }
    }
    case files(number: Int)
    case tags
}

Your questions about the navigation stack are conevered in Understanding the navigation stack and Restore state for navigation paths. Review those and reply back if you have any further questions.

Hi DTS Engineer, thank you for reply and providing a suggestion. I understand your suggestion, and I also have read the two articles you shared. How can I apply it to my problem where NavigationSplitView clearing the NavigationStack's path when selection changes? Is your suggestion to use willSet/didSet on path and back up the path there?

Accepted Answer

willSet didn't work on @State sidebarSelection; it was never getting called. But onChange(of:) worked nicely.

Here is the change I made:

List(SidebarSelection.allCases, selection: $sidebarSelection) { selection in
  switch selection {
    case .files: Label("Files", image: "custom.square.stack")
    case .tags: Label("Tags", systemImage: "grid")
    case .trash: Label("Trash", systemImage: "trash")
  }
}
.onChange(of: sidebarSelection) {
  if sidebarSelection != .files {
    backup = path
    print("backup: \(path.count)")
  } else {
    path = backup
    print("restore: \(backup.count)")
  }
}

Thanks for a hint!

As a note:

Still, the animation is quite clunky when selection changes, because the path clearing is still happening during transition, which is shown on the toolbar and title. It looks like NavigationSplitView is always intended for drill-down scenario where selecting a sidebar always leading to Root is acceptable.

Probably I need to find a solution with TabView, but let me know if I'm wrong.

NavigationStack within NavigationSplitView's detail column clears the path when disappearing
 
 
Q