In iOS 16, SwiftUI introduced the new navigation API, requiring that navigation state should be driven by data. Therefore, one need to design the navigation data model before adopting the new navigation API.
I encountered a case where I find it rather difficult to design a data model for SwiftUI Navigation APIs in iOS 16. To clearly state the case, I would provide the following senario.
The app contains two major sections: Fruit Store and Recipes. In Fruit Store, user may browse and purchase different kinds of fruit; In Recipes, user may browse online recipes to make some drinks.
Both section has some rather complicated navigation system. In Fruit Store section, user start from the home page and view some fruit detail page, where he may again navigate to some fruit category page, etc. So is the case in Recipes section. In a word, neither of the section's navigation system can be categorized by a single type.
I wish to use a three column design, i.e., NavigationSplitView
, as the following image shows.
I've read Apple's NavigationCookbook demo, but it didn't solve my problem. It's detail page has only one type, so using a single value to control the detail page would be an elegant solution. However that doesn't apply in my case, as my detail page has multiple types.
I wonder if there is a simple solution to solve this problem, which would be very useful for people adopting SwiftUI in large scale projects.
This seems pretty straight forward. Using the initialiser
NavigationSplitView(sidebar: () -> Sidebar, content: () -> Content, detail: () -> Detail)
you just need to keep track of the sidebar selection. Using NavigationLink
s in the content
, the NavigationSplitView
handles showing the correct detail views.
struct ContentView: View {
@State private var sidebarSelection: SidebarType?
var body: some View {
NavigationSplitView {
List(SidebarType.allCases, id: \.self, selection: $sidebarSelection) { type in
Text(type.name)
}
} content: {
switch sidebarSelection {
case .recipes:
List(Recipe.allCases) { recipe in
NavigationLink(recipe.name) {
Text("Selected: \(recipe.name)")
}
}
case .fruits:
List(Fruit.allCases) { fruit in
NavigationLink(fruit.name) {
Text("Selected: \(fruit.name)")
}
}
default:
Text("Select something")
}
} detail: {
switch sidebarSelection {
case .recipes:
Text("Select a recipe")
case .fruits:
Text("Select a fruit")
default:
EmptyView()
}
}
}
}
struct Recipe: CaseIterable, Identifiable, Hashable {
let id = UUID()
let name: String
static let allCases: [Recipe] = (1...9).map { Recipe(name: "Recipe \($0)")}
}
struct Fruit: CaseIterable, Identifiable, Hashable {
let id = UUID()
let name: String
static let allCases: [Fruit] = (1...9).map { Fruit(name: "Fruit \($0)")}
}
enum SidebarType: CaseIterable, Identifiable {
var id: Self { self }
case recipes, fruits
var name: String {
switch self {
case .recipes: return "Recipes"
case .fruits: return "Fruit Store"
}
}
}