Using NavigationSplitView with NavigationStack in it with variable navigation depth (2 and 3 levels) — unexpected back navigation on iPhone

I’m building a cross-platform app targeting macOS, iPad, and iPhone. My app currently uses both 2-level and 3-level navigation workflows:

3-level navigation:

  • First level: Categories
  • Second level: List of items in the selected category
  • Third level: Detail view for a specific item

2-level navigation:

  • First level: Category
  • Second level: A singleton detail view (for example, StatusView). It does not have concept of List.

After watching a couple of WWDC videos about multi-platform navigation, I decided to go with NavigationSplitView.

However, on macOS, a 3-column NavigationSplitView felt a bit overwhelming to my eyes when the third column was empty—especially for the occasional 2-level navigation case. So I removed the third column and instead embedded a NavigationStack in the second column. According to the official Apple documentation, this is supported:

You can also embed a NavigationStack in a column.

The code with NavigationStack in NavigationSplitView works fine on macOS.

But on iPhone, for the same code I’m seeing unexpected behavior:

The first time I tap on the “Actions” category, it briefly shows the “Select an item” view, and then automatically pops back to the all-categories view. If I tap the same "Actions" category again, it shows the list of actions correctly, and everything works fine until I terminate and relaunch the app. Here is a minimal reproducible example:

import SwiftUI

struct StatusView: View {
    var body: some View {
        NavigationStack {
            List {
                Text("Heartbeat: OK")
                Text("Connected to backend: OK")
            }
        }
    }
}

struct ActionListView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink(value: "Action 1 value") {
                    Text("Action 1 label")
                }
                NavigationLink(value: "Action 2 value") {
                    Text("Action 2 label")
                }
            }
        }
        .navigationDestination(for: String.self) { action in
            Text(action)
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationSplitView {
            List {
                NavigationLink(value: "Actions") {
                    Text("Actions (3 level)")
                }
                NavigationLink(value: "Modes") {
                    Text("Modes (3 level)")
                }
                NavigationLink(value: "State") {
                    Text("Status (2 level)")
                }
            }
            .navigationDestination(for: String.self) { category in
                switch category {
                case "Actions":
                    ActionListView()
                case "Modes":
                    Text("Modes View")
                case "State":
                    StatusView()
                default:
                    Text("Unknown Category")
                }
            }
        } detail: {
            Text("Select an item")
            
        }
    }
}

Questions and considerations:

  1. How can I prevent this unexpected automatic pop back to the root view on iPhone the first time I select a category?

  2. Future-proofing for more than 3 level navigation: In the future, I may allow users to navigate beyond three levels (e.g., an item in one category can reference another item in a different category). Is it correct to assume that to support this with back navigation, I’d need to keep using NavigationStack inside NavigationSplitView?

  3. Is embedding NavigationStack in a 2 column NavigationSplitView the only practical approach to handle mixed 2 and 3 navigation depth if I don't want the third column to be ever empty?

  4. On macOS, NavigationStack alone doesn’t feel appropriate for sidebar-based navigation. Does it mean everyone on macOS pretty much always use NavigationSplitView?

Any advice or examples would be appreciated. Thanks!