简体   繁体   中英

any Identifiable can't conform to 'Identifiable'

update: add same error about Hashable


I have created an Identifiable compliant protocol and compliant structures. Then, when I create the list and reference it in ForEach , I get the error Type 'any TestProtocol' cannot conform to 'Identifiable' (I get the same error about Hashable ).

How should I fix this program?

If I write ForEach(list, id: \.id) , it works, but I don't think it makes sense to be Identifiable compliant.

import SwiftUI

protocol TestProtocol: Identifiable, Hashable {
    var id: UUID { get set }
    var name: String { get set }
    
    func greeting() -> String
    static func == (lhs: Self, rhs: Self) -> Bool
}

extension TestProtocol {
    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.id == rhs.id
    }
}

struct Person: TestProtocol {
    var id = UUID()
    var name: String
    
    func greeting() -> String {
        return "my name is \(name) and I'm a human."
    }
}

struct Dog: TestProtocol {
    var id = UUID()
    var name: String
    
    func greeting() -> String {
        return "my name is \(name) and I'm a dog."
    }
}

struct ContentView: View {
    var list: [any TestProtocol] = [Person(name: "p1"), Dog(name: "d1")]
    @State var selected: any TestProtocol
    
    var body: some View {
        VStack {
            Picker(selection: $selected) { // Type 'any TestProtocol' cannot conform to 'Hashable'
                ForEach(list) { l in // Type 'any TestProtocol' cannot conform to 'Identifiable'
                    Text(l.greeting()).tag(l) // Type 'any TestProtocol' cannot conform to 'Hashable'
                }
            } label: {
                Text("select")
            }
        }
    }
}

Your error message complaining about Hashable is a "red hering". The protocol TestProtocol , and therefor all structs conforming to it, conforms to Hashable .

let person = Person(name: "IAmHashable")
print(person.hashValue)

The reason this is failing is within the Picker . It needs a concrete type and not a protocol. One solution would be to create a "Container" type and a custom binding that handles this.

struct Container: Identifiable, Hashable{
    //implement the same equality as in your TestProtocol
    static func == (lhs: Container, rhs: Container) -> Bool {
        rhs.wrapped.id == lhs.wrapped.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(wrapped)
    }
    
    var wrapped: any TestProtocol
    //for convenience
    var id: UUID {wrapped.id}
}

and the ContentView:

struct ContentView: View {
    
    let startArr: [any TestProtocol] = [Person(name: "p1"), Dog(name: "d1")]
    @State private var selected: (any TestProtocol)?

    var body: some View {
        // list of wrapped protocols
        var list: [Container] = { startArr.map{Container(wrapped: $0)}}()
        // binding
        let selectionBinding: Binding<Container> = .init {
            let returninstance = list.first { cont in
                cont.id == selected?.id
            }
            return returninstance ?? list[0]
        } set: { container in
            selected = container.wrapped
        }
        
        // viewCode
        VStack {
            Picker(selection: selectionBinding) {
                ForEach(list) { l in
                    Text(l.wrapped.greeting())
                        .tag(l)
                }
            } label: {
                Text("select")
            }
            // confirmation selection changed
            Text(selected?.name ?? "no Selection")
        }
    }
}

Remarks:

This solution has a few drawbacks :

  • your initial array startArr should never be empty, else return returninstance?? list[0] return returninstance?? list[0] will break you code. This can be handled, but I think this is out of the scope of this question.
  • the equality comparison of Container needs to be the same as in TestProtocol as you cannot compare two any Protocol s
  • on the appearance of ContainerView selected will be nil until something is selected. Any usage, eg: the Text element in this case, needs to deal with this. But you could probably set this in .onApear .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM