I'm having some trouble getting my head around conforming existential variables.
Since Animal
here will always conform to Hashable
, I would imagine that any Animal
would also have to conform to Hashable
.
However, the error I'm seeing is causing me to think otherwise. Can someone help explain why this is occurring, and if possible, help me resolve it?
import SwiftUI
import PlaygroundSupport
protocol Animal: Hashable {
var name: String { get }
}
struct Cow: Animal {
let name: String
}
struct Chicken: Animal {
let name: String
}
let anAnimalList: [any Animal] = [Cow(name: "Aaron"), Chicken(name: "Billy"), Cow(name: "Charlie"), Chicken(name: "Delilah")]
struct myView: View {
@State private var anAnimal: (any Animal)?
var body: some View {
VStack {
List(anAnimalList, id: \.self, selection: $anAnimal) { animal in // ERROR: Type 'any Animal' cannot conform to 'Hashable'
Text("Animal")
}
Text("Animal is \(anAnimal?.name ?? "Null")")
}
}
}
PlaygroundPage.current.setLiveView(myView())
CodePudding user response:
Assuming we want to find a way of how to list [any Animal]
but not "why this shit happens" :) (see corresponding WWDC22 session to find answer on second one)
Tested with Xcode 14b3 / iOS 16
Let's jump into this hole...
There are two places of reported error in original code:
List(anAnimalList, id: \.self, // << 1st place selection: $anAnimal // << 2nd place ) { animal in Text("Animal") }
1st place - because Animal
is not identifiable and self
relates to instance of concrete type, so we should not use \.self
(because compiler will not be able to infer concrete type), but identify directly by \.id
So here is a possible fix for the first issue:
protocol Animal: Hashable, Identifiable {
var id: UUID { get }
var name: String { get }
}
struct Cow: Animal {
let id = UUID()
let name: String
}
struct Chicken: Animal {
let id = UUID()
let name: String
}
var body: some View {
VStack {
List(anAnimalList, id: \.id) { animal in // << no error !!
Text("Animal: \(animal.name)")
}
}
}
2nd place - now about selection; the story is the same, swift should know "concrete type of selection" or could be able "infer it from code", both are impossible at compile type, so instead trying to break the wall with a head, let's find a door...
A possible solution is to use for selection something known and concrete in the context... we've just added it above - the 'id'
So here a fix for second part
@State private var anAnimalID: UUID? // << here !!
var body: some View {
VStack {
List(anAnimalList, id: \.id, selection: $anAnimalID) { animal in
Text("Animal: \(animal.name)")
}
if let animal = anAnimalList.first { $0.id == anAnimalID } {
Text("Animal is \(animal.name)")
}
}
}