Jake Freeland
Jake Freeland

Reputation: 23

Generic type of a struct - SwiftUI

I'm trying to pass a struct as a generic type (seen in Model). I tried using AnyView to represent my structs, but the complier errors out on build with:

Could not cast value of type 'EagleNation_macOS.NewsView' (0x10d7ff898) to 'SwiftUI.AnyView' (0x7fff82e27670).
2021-03-17 12:35:45.932135-0500 EagleNation-macOS[24102:2436828] Could not cast value of type 'EagleNation_macOS.NewsView' (0x10d7ff898) to 'SwiftUI.AnyView' (0x7fff82e27670).
Could not cast value of type 'EagleNation_macOS.NewsView' (0x10d7ff898) to 'SwiftUI.AnyView' (0x7fff82e27670).

What type do I use to represent a struct in a generic in SwiftUI?

Model:

import Foundation

struct MainBackend<Destination> {
    let navDirs = [
        NavDir(title: "News", dest: NewsView() as! Destination),
        NavDir(title: "Bulletin", dest: BulletinView() as! Destination),
        NavDir(title: "Clubs", dest: ClubsView() as! Destination),
    ]
    
    struct NavDir: Identifiable {
        let title: String
        let dest: Destination
        var id = UUID()
    }
}

ViewModel:

import SwiftUI

class MainCommunication {
    private var backend = MainBackend<AnyView>()
    
    // MARK: - Access to Model
    var navDirs: [MainBackend<AnyView>.NavDir] {
        backend.navDirs
    }
}

View:

import SwiftUI

struct MainView: View {
    var body: some View {
        NavigationView {
            NavBar { NavDir in
                NavDir.dest
            }
            NewsView()
        }
    }
}

struct NavBar<Destination: View>: View {
    let navDirs = MainCommunication().navDirs
    let buildDestination: (MainBackend<AnyView>.NavDir) -> Destination
    
    var body: some View {
        VStack() {
            List(navDirs) { NavDir in
                NavigationLink(destination: buildDestination(NavDir)) {
                    Text(NavDir.title)
                }
            }
            .listStyle(SidebarListStyle())
        }
    }
}

Upvotes: 2

Views: 682

Answers (1)

jnpdx
jnpdx

Reputation: 52407

You're going to want them to be AnyView -- that's the only way to erase the types into a homogenous array. But, you can't cast to AnyView -- you have to wrap them in AnyView().

This means you don't actually need the generic at all:


struct MainBackend {
    let navDirs = [
        NavDir(title: "News", dest: AnyView(NewsView())),
        NavDir(title: "Bulletin", dest: AnyView(BulletinView())),
        NavDir(title: "Clubs", dest: AnyView(ClubsView())),
    ]
    
    struct NavDir: Identifiable {
        let title: String
        let dest: AnyView
        var id = UUID()
    }
}

class MainCommunication {
    private var backend = MainBackend()
    
    // MARK: - Access to Model
    var navDirs: [MainBackend.NavDir] {
        backend.navDirs
    }
}

struct MainView: View {
    var body: some View {
        NavigationView {
            NavBar { NavDir in
                NavDir.dest
            }
        }
    }
}

struct NavBar<Destination: View>: View {
    let navDirs = MainCommunication().navDirs
    let buildDestination: (MainBackend.NavDir) -> Destination
    
    var body: some View {
        VStack() {
            List(navDirs) { NavDir in
                NavigationLink(destination: buildDestination(NavDir)) {
                    Text(NavDir.title)
                }
            }
            .listStyle(SidebarListStyle())
        }
    }
}

See also: How to have a dynamic List of Views using SwiftUI

Upvotes: 1

Related Questions