UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Stored property type 'CGPoint' does not conform to protocol 'Hashable'... the problem is that the point is @State

Forums > SwiftUI

Imagine a card board. All cards are on a view I call CardBoard.

All cards are on an array:

var cards:[Card]

Every card has its own position.

This is Card

struct Card: View {
    let id = UUID()

    var name:String
    var code:String
    var filename:String
    @State var position = CGPoint(x: 200, y: 250)

    init(name:String, code:String, filename:String) {
      self.name = name
      self.code = code
      self.filename = filename
    }

    var body: some View {
      Image(filename)
        .position(position)
    }
  }

Now I want to draw them on the screen.

var body: some View {
    ZStack {
      ForEach(cards, id:\.self) {card in

      }
    }
  }

when I try this, Xcode tells me

Generic struct 'ForEach' requires that 'Card' conform to 'Hashable'

when I add Hashable to Card

struct Card: View, Hashable {
  1. Stored property type 'CGPoint' does not conform to protocol 'Hashable', preventing synthesized conformance of 'Card' to 'Hashable'

if position was not @State, I would simply add this to fix the problem

extension CGPoint : Hashable {
   public func hash(into hasher: inout Hasher) {
     hasher.combine(x)
     hasher.combine(y)
   }
}

but this will not work for point as a @State...

any ideas?

3      

It's because of the id: \.self. Since Card already has an id property, you can conform it to Identifiable and just use ForEach(cards)

You should only use the id parameter if the objects you are looping over aren't Identifiable. By using \.self, you are telling SwiftUI that each Card object can use itself as a unique identifier (in the same way, e.g., String or Int can be), but it must be Hashable for that to happen and, as youve found out, that causes problems with some types.

4      

Thanks for the answer I did what you said and the problem is solved. I am not sure if I understand correcly when an object must be hashable.

3      

I am not sure if I understand correcly when an object must be hashable.

So ForEach needs to be able to uniquely identify the elements it is looping over. The simplest way to do this is for the items to conform to Identifiable, which means each one has an id property and ForEach can use that property to identify each item. That's when you see the form like this:

ForEach(identifiableItems) { item in
...
}

But sometimes conforming to Identifiable just isn't possible, like in the case of an array of Strings. While you could conform String to Identifiable, say by giving it a computed id property that just returns self, it's not considered a good idea to conform types you don't control to protocols you don't control, so I wouldn't suggest it.

Instead, you can use this form of ForEach:

ForEach(nonIdentifiableItems, id: \.self) { item in
...
}

and for the id property use a key path indicating some property (stored or computed) that uniquely identifies each item. In the case of types like String or Int, you can use \.self to say that each item itself is unique. "We're not in Kansas anymore" is different from "Come with me if you want to live", just as 5 is different from 222. And how does ForEach know they are different? By taking a hash of whatever property is indicated by the id parameter. Which means that property has to be hashable (i.e., conform to the Hashable protocol).

You don't have to use \.self there; any property that is Hashable will do. Maybe you are looping through an array of structs that each have a unique URL stored in a property called url; you could use id: \.url. And so on. Point is, whatever property you pick has to be Hashable so that the ForEach can uniquely identify each item it is looping through. (You will run into problems—sometimes subtle ones—if your items aren't uniquely identifiable.)

Make sense?

3      

Ah, the fact of it being hashable eases that task because if it has to compare every letter of each string, character by character it would take longer than previously hasing the strings and compare the hash? Interesting.

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.