swiftuiswiftui-navigationlinknavigationsplitview

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


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?


Solution

  • 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 ")
                }
            }
            
        }
    }