2

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())
        }
    }
}

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

3 Comments

jnpdx, In your answer, please change the word "erase" to "ease" - which is what I think you meant.
@KeithB no - "erase" is exactly what I meant. You can read about Type Erasure here: en.wikipedia.org/wiki/Type_erasure
jnpdx, I apologize. I was not aware of that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.