Still playing with the NavigationSplitView, but found no solution for the following.
I need/want to use here the .navigationDestination(for: .....)
modifier and I want to call the different DetailViews with a switch but I can't get it to work.
What is wrong? Did I miss something here?
Data and Structs
import Foundation
import SwiftUI
struct Item1: Identifiable, Hashable {
var id = UUID()
var name: String
var place: String
var date: Date
}
struct Item2: Identifiable, Hashable {
var id = UUID()
var name: String
var place: String
var birthdate: Date
}
struct Item3: Identifiable, Hashable {
var id = UUID()
var name: String
var city: String
var dDay: Date
}
// Create some sample data
let items1 = [
Item1(name: "Alice", place: "Wonderland", date: Date()),
Item1(name: "Bob", place: "Bikini Bottom", date: Date()),
Item1(name: "Charlie", place: "Chocolate Factory", date: Date()),
Item1(name: "David", place: "Dungeon", date: Date()),
]
let items2 = [
Item2(name: "Alice", place: "Wonderland", birthdate: Date()),
Item2(name: "Bob", place: "Bikini Bottom", birthdate: Date()),
Item2(name: "Charlie", place: "Chocolate Factory", birthdate: Date()),
Item2(name: "David", place: "Dungeon", birthdate: Date()),
]
let items3 = [
Item3(name: "Alice", city: "Wonderland", dDay: Date()),
Item3(name: "Frank", city: "France", dDay: Date()),
Item3(name: "Harry", city: "Hogwarts", dDay: Date()),
Item3(name: "Iris", city: "Italy", dDay: Date()),
Item3(name: "Jack", city: "Japan", dDay: Date())
]
Define the views for the detail area
struct DetailView1: View {
var item: Item1
var body: some View {
VStack {
Text ("DetailView1")
Text(item.name)
.font(.largeTitle)
Text(item.place)
.font(.title)
Text(item.date, style: .date)
.font(.title2)
}
}
}
struct DetailView2: View {
var item: Item2
var body: some View {
VStack {
Text ("DetailView2")
Text(item.name)
.font(.largeTitle)
Text(item.place)
.font(.title)
Text(item.birthdate, style: .date)
.font(.title2)
}
}
}
struct DetailView3: View {
var item: Item3
var body: some View {
VStack {
Text ("DetailView3")
Text(item.name)
.font(.largeTitle)
Text(item.city)
.font(.title)
Text(item.dDay, style: .date)
.font(.title2)
}
}
}
MyNavigationView description
struct MyNavigationView: View {
let section1 = items1
let section2 = items2
let section3 = items3
// Use a state variable to store the selected item
@State var selectedItem: AnyView? // Item?
var body: some View {
NavigationSplitView {
List {
Section(header: Text("Section 1")) {
ForEach(section1) { item in
// Use the new NavigationLink with value and label
NavigationLink(value: item, label: {
Text(item.name)
})
}
}
Section(header: Text("Section 2")) {
ForEach(section2) { item in
// Use the new NavigationLink with value and label
NavigationLink(value: item, label: {
Text(item.name)
})
}
}
Section(header: Text("Section 3")) {
ForEach(section3) { item in
// Use the new NavigationLink with value and label
NavigationLink(value: item, label: {
Text(item.name)
})
}
}
}
.listStyle(SidebarListStyle())
.navigationDestination(for: AnyView) { viewType in
switch viewType {
case let viewType as DetailView1:
DetailView1($selectedItem)
.navigationTitle("Detail View1")
case let viewType as DetailView2:
DetailView2($selectedItem)
.navigationTitle("Detail View2")
case let viewType as DetailView3:
DetailView3($selectedItem)
.navigationTitle("Detail View3")
default:
EmptyView()
.navigationTitle("No Detail View")
}
}
} detail: {
Text("Select an item ")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
How can I select the right DetailView to show up in the detail view? When I use I get the error that AnyView is not Hashable. How can I solve this to understand what direction I should go?
First, the list selection should not have type AnyView
. The list selection should be some data that represents what is being selected, not a view. That said, you don't need this selection - since you are not driving the NavigationSplitView
with a list, but with navigationDestination
s.
Secondly, the types you pass to navigationDestination(for:destination:)
should be the types of your data. It should be the same type as the values you passed to NavigationLink(value:label:)
.
Since you have 3 types of items, you should apply this three times:
.navigationDestination(for: Item1.self) { item in
DetailView1(item: item).navigationTitle("Detail View1")
}
.navigationDestination(for: Item2.self) { item in
DetailView2(item: item).navigationTitle("Detail View2")
}
.navigationDestination(for: Item3.self) { item in
DetailView3(item: item).navigationTitle("Detail View3")
}
Thirdly, the detail view of the split view is already determined by navigationDestination
s above. You can't have a separate view in detail:
when selection != nil
. Just handle the case when selection == nil
:
} detail: {
Text("Select an item ")
}
Alternatively, you can use a list selection type like this:
enum Item: Hashable {
case one(Item1)
case two(Item2)
case three(Item3)
}
This is basically a way to have a single selection type for all 3 kinds of items. You would need to wrap the ItemN
s into this type when creating the navigation links. Other than that, it's the same as a List
-driven navigation split view:
struct ContentView: View {
let section1 = items1
let section2 = items2
let section3 = items3
@State var selection: Item?
var body: some View {
NavigationSplitView {
List(selection: $selection) {
Section(header: Text("Section 1")) {
ForEach(section1) { item in
NavigationLink(value: Item.one(item), label: {
Text(item.name)
})
}
}
Section(header: Text("Section 2")) {
ForEach(section2) { item in
NavigationLink(value: Item.two(item), label: {
Text(item.name)
})
}
}
Section(header: Text("Section 3")) {
ForEach(section3) { item in
NavigationLink(value: Item.three(item), label: {
Text(item.name)
})
}
}
}
.listStyle(.sidebar)
} detail: {
switch selection {
case .one(let item1):
DetailView1(item: item1).navigationTitle("Detail View1")
case .two(let item2):
DetailView2(item: item2).navigationTitle("Detail View2")
case .three(let item3):
DetailView3(item: item3).navigationTitle("Detail View3")
case nil:
Text("Select an item ")
}
}
}
}