NavigationSplitView - last part not working - Why and what's the difference?

68 Views Asked by At

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?

1

There are 1 best solutions below

8
Sweeper On BEST ANSWER

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 navigationDestinations.

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 navigationDestinations 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 ItemNs 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 ")
            }
        }
        
    }
}