How to use protocol in List in SwiftUI

Issue #446

Suppose we have Service protocol, and want to use in List

1
2
3
protocol Service {
var name: String { get }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct MainView: View {
let services = [
Car()
Plane()
]

var body: some View {
List(services) { service in
HStack {
Image(service.name)
Text(service.name)
}
}
}
}

This is not possible because item in List needs to conform to Identifiable

Protocol type ‘Service’ cannot conform to ‘Identifiable’ because only concrete types can conform to protocols

Type eraser

In the same way that SwiftUI uses type eraser, for example AnyView, we can introduce AnyService to work around this

1
2
3
4
5
6
7
var body: some View {
if useImage {
return AnyView(Image("my image"))
} else {
return AnyView(Text("my text"))
}
}

Make AnyService conform to Identifiable

1
2
3
4
5
6
7
8
9
struct AnyService: Identifiable {
let id: String
let service: Service

init(_ service: Service) {
self.service = service
self.id = service.name
}
}

Then in our View, we just need to declare services wrapped inside AnyService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct MainView: View {
let services = [
AnyService(Car()),
AnyService(Plane())
]

var body: some View {
List(services) { anyService in
HStack {
Image(anyService.service.name)
Text(anyService.service.name)
}
}
}
}

A bit refactoring, we can just declare normal services and map them

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct MainView: View {
let services: [Service] = [
Car(),
Plane()
]

var body: some View {
List(services.map({ AnyService($0 })) { anyService in
HStack {
Image(anyService.service.name)
Text(anyService.service.name)
}
}
}
}

Comments