UberJason
UberJason

Reputation: 3163

SwiftUI: heterogeneous collection of destination views for NavigationLink?

I’m building like a demo app of different examples, and I’d like the root view to be a List that can navigate to the different example views. Therefore, I tried creating a generic Example struct which can take different destinations Views, like this:

struct Example<Destination: View> {
    let id: UUID
    let title: String
    let destination: Destination

    init(title: String, destination: Destination) {
        self.id = UUID()
        self.title = title
        self.destination = destination

    }
}

struct Example1View: View {
    var body: some View {
        Text("Example 1!")
    }
}

struct Example2View: View {
    var body: some View {
        Text("Example 2!")
    }
}

struct ContentView: View {
    let examples = [
        Example(title: "Example 1", destination: Example1View()),
        Example(title: "Example 2", destination: Example2View())
    ]

    var body: some View {
        List(examples, id: \.id) { example in
            NavigationLink(destination: example.destination) {
                Text(example.title)
            }
        }
    }
}

Unfortunately, this results in an error because examples is a heterogeneous collection:

enter image description here

I totally understand why this is broken; I’m creating a heterogeneous array of examples because each Example struct has its own different, strongly typed destination. But I don’t know how to achieve what I want, which is an array that I can make a List out of which has a number of different allowed destinations.

I’ve run into this kind of thing in the past, and in the past I’ve gotten around it by wrapping my generic type and only exposing the exact properties I needed (e.g. if I had a generic type that had a title, I would make a wrapper struct and protocol that exposed only the title, and then made an array of that wrapper struct). But in this case NavigationLink needs to have the generic type itself, so there’s not a property I can just expose to it in a non-generic way.

Upvotes: 3

Views: 1218

Answers (1)

RPatel99
RPatel99

Reputation: 8096

You can use the type-erased wrapper AnyView. Instead of making Example generic, make the destination view inside of it be of type AnyView and wrap your views in AnyView when constructing an Example.

For example:

struct Example {

    let id: UUID
    let title: String
    let destination: AnyView

    init(title: String, destination: AnyView) {
        self.id = UUID()
        self.title = title
        self.destination = destination
    }
}

struct Example1View: View {
    var body: some View {
        Text("Example 1!")
    }
}

struct Example2View: View {
    var body: some View {
        Text("Example 2!")
    }
}

struct ContentView: View {
    let examples = [
        Example(title: "Example 1", destination: AnyView(Example1View())),
        Example(title: "Example 2", destination: AnyView(Example2View()))
    ]

    var body: some View {
        NavigationView {
            List(examples, id: \.id) { example in
                NavigationLink(destination: example.destination) {
                    Text(example.title)
                }
            }
        }
    }
}

Upvotes: 7

Related Questions