SwiftUI - View showing Elements conforming to a Protocol and and using == for the Elements

Viewed 609

I need to display arrays of different structs, conforming to a common Protocol, in a View.
As advised in SwiftUI - View showing Elements conforming to a Protocol and ForEach over them I tried like this - it works fine!

Now I need to check elements of the array for Equality.
Letting the Protocol conform to Equatable does not compile -
It gets the error: Protocol 'Suggest' can only be used as a generic constraint because it has Self or associated type requirements.

//protocol Suggest :Equatable {
protocol Suggest  {
  var desc: String { get }
}

struct Person : Suggest {
  var surname : String
  var name: String
  
  var id: String { return name }
  var desc: String { return name }
}

struct Book : Suggest {
  var titel : String
  var abstact : String
  
  var id: String { return titel }
  var desc: String { return titel }
}


let books = [ Book(titel: "book 1", abstact: "abstract1"),
              Book(titel: "book 2", abstact: "abstract2")
            ]

let persons = [ Person(surname: "John", name: "Doe"),
                Person(surname: "Susan", name: "Smith"),
                Person(surname: "Frank", name: "Miller")
              ]


struct ContentView: View {
  var body: some View {
    VStack {
      SuggestList(list: books)
      SuggestList(list: persons)
    }
  }
}

struct SuggestList: View {
  var list : [Suggest]
// this line does not compile, if Suggest conforms to Equitable
// "Protocol 'Suggest' can only be used as a generic constraint because it has Self or associated type requirements" 
  
  var body: some View {
    List(list.indices, id: \.self) { index in
      Text(list[index].desc)
//        .onTapGesture {
//          if list.contains(list[index]){print ("hello")}
//        }
    }
  }
}
2 Answers

you need use <SuggestType: Suggest> and also make Suggest protocol Equatable and then use and define == in Person and Book


struct ContentView: View {
    var body: some View {
        VStack {
            SuggestList(list: books)
            SuggestList(list: persons)
        }
    }
}

protocol Suggest: Equatable  {
    var desc: String { get }
}

struct Person: Suggest {
    
    var surname: String
    var name: String
    
    var id: String { return name }
    var desc: String { return name }
    
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name
    }
    
}

struct Book: Suggest {
    
    var titel: String
    var abstact: String
    
    var id: String { return titel }
    var desc: String { return titel }
    
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.titel == rhs.titel
    }
    
    
}



let persons = [Person(surname: "John", name: "Doe"),
               Person(surname: "Susan", name: "Smith"),
               Person(surname: "Frank", name: "Miller")]


let books = [Book(titel: "book 1", abstact: "abstract1"),
             Book(titel: "book 2", abstact: "abstract2")]




struct SuggestList<SuggestType: Suggest>: View {
    
    var list : [SuggestType]

    
    var body: some View {
        List(list.indices, id: \.self) { index in
            Text(list[index].desc)
                .onTapGesture {
                    if list.contains(list[index]){ print(list[index].desc) }
                }
        }
    }
}

This Answer is belong to question in comments part! About Equatable function.

If you do not define Equatable function explicitly, then Xcode would take care itself if it can infer it by itself, some times in complex struct it will ask you to show it when instance of your struct are equal, but when you define Equatable function explicitly Xcode would apply your custom rule, for example I create 2 type of Person, which in first one PersonV1 we did not define == for it but in second one PersonV2 we did defined! So Xcode would be taking all persons with same name equal in PersonV2 if even they have deferent surname. try this down code for more real testing example. And any update for surname in PersonV2 would not take any place, because it does not count in determine if 2 instance of PersonV2 are equal or not! Once you initialized an instance of PersonV2, the surname will not be updatable anymore. you can try to update but it will not applied because in make no deference if this instance is the same or not!

Notice: We could make PersonV2 equality function to re act to surname change as well with this code, but I think you want to work just with name like in your question:

static func == (lhs: PersonV2, rhs: PersonV2) -> Bool {
    return lhs.name == rhs.name && lhs.surname == rhs.surname
}



struct ContentView: View {

    @State private var person1: PersonV1 = PersonV1(surname: "Frank", name: "Miller")
    @State private var person2: PersonV2 = PersonV2(surname: "John", name: "Doe")

    var body: some View {
        
        VStack(spacing: 50.0) {
        
        VStack(spacing: 20.0) {

            Button("update name of person1") { person1.name += " updated!" }
            Button("update surname of person1") { person1.surname += " updated!" }

        }
        .padding()
        .background(Color.yellow)
        .onChange(of: person1) { newValue in print("onChange for person1:", newValue) }
        
        
        
        
        VStack(spacing: 20.0) {

            Button("update name of person2") { person2.name += " updated!" }
            Button("update surname of person2") { person2.surname += " updated!" }

        }
        .padding()
        .background(Color.red)
        .onChange(of: person2) { newValue in print("onChange for person2:", newValue) }
        
        }
        
    }
}

protocol Suggest: Equatable  {
    var desc: String { get }
}



struct PersonV1: Suggest {
    
    var surname: String
    var name: String
    
    var id: String { return name }
    var desc: String { return name }
   
}


struct PersonV2: Suggest {
    
    var surname: String
    var name: String
    
    var id: String { return name }
    var desc: String { return name }

    static func == (lhs: PersonV2, rhs: PersonV2) -> Bool {
        return lhs.name == rhs.name
    }
    
}
Related