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:
-
How can I prevent this unexpected automatic pop back to the root view on iPhone the first time I select a category?
-
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?
-
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?
-
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!