3

I have a little question about conforming to Identifiable in SwiftUI.

There are situations where we are required to have a given type MyType to conform to Identifiable.

But I am facing a case where I am required to have [MyType] (an array of MyType) to conform to Identifiable.

I have MyType already conforming to Identifiable. What should I do to also make [MyType] to conform to Identifiable?

Michel
  • 10,303
  • 17
  • 82
  • 179

2 Answers2

7

I suggest embedding [MyType] in a struct, then having the struct conform to Identifiable. Something like this:

struct MyType: Identifiable {
    let id = UUID()
}
struct Container: Identifiable {
    let id = UUID()
    var myTypes = [MyType]()
}

Usage:

struct ContentView: View {
    let containers = [
        Container(myTypes: [
            MyType(),
            MyType()
        ]),
        Container(myTypes: [
            MyType(),
            MyType(),
            MyType()
        ])
    ]
    
    var body: some View {

        /// no need for `id: \.self`
        ForEach(containers) { container in
            ...
        }
    }
}
aheze
  • 24,434
  • 8
  • 68
  • 125
5

You can write an extension to conform an Array to Identifiable.

Since extensions can't contain stored properties, and also because it makes sense that two arrays that are the "same" to also have the same id, you'd need to compute the id based on the contents of an array.

The simplest approach here is if you can conform your type to Hashable:

extension MyType: Hashable {}

This also makes [MyType] conform to Hashable, and since id could be any Hashable, you could use the array itself as its own id:

extension Array: Identifiable where Element: Hashable {
   public var id: Self { self }
}

Or, if you want, the id could be an Int:

extension Array: Identifiable where Element: Hashable {
   public var id: Int { self.hashValue }
}

Of course, you can do this just for your own type where Element == MyType, but that type would need to be public.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • @New_Dev. Before seing this answer, I actually gave it a try based on your suggestion in the comment you had previously written. But I failed. With this new idea of using Hashable, it may work, but I have to say I don't really like the idea of "using the array itself as its own id". – Michel Jun 21 '21 at 13:33
  • @Michel. why not? `Identifiable` just needs a `Hashable` - so, under the covers it just uses the hash. – New Dev Jun 21 '21 at 13:36
  • @Michel, If this is uncomfortable for you, you can make the `id` an `Int` using the hash value directly - see updated answer – New Dev Jun 21 '21 at 13:39
  • Yes. I guess that can also work. One other interesting point of your suggestion, you wrote "because it makes sense that two arrays that are the 'same' to also have the same id". I am not sure this is the way an id is meant to work. An id should allow you to make the difference between the nth element and the mth element regardless of whether they are equal or not. Tell me if I am wrong. – Michel Jun 22 '21 at 05:28
  • @Michel - I think it depends on what you want to achieve, and how you want to treat "equal" objects. This impacts the animation of insertion and removal in a list. If you need every object to be unique, then you use something like `UUID`. Or, you can use object's index in an array as its identifier (but then the object does not need to be `Identifiable`). – New Dev Jun 22 '21 at 12:17
  • OK. Well, having an index is a way to make it identifiable. Isn't it? – Michel Jun 23 '21 at 03:42
  • 1
    @Michel - it's not quite so. Having an index means that you identify the list objects based on something external to the object. So, it doesn't make the object `Identifiable`, but it does use something else - the index - as the `id`. I'm specifically referring to `ForEach(list.indices, id: \.self) { index in ... }`. Your question was about making the object (in this case, `[MyType]`) as the `Identifiable`. – New Dev Jun 23 '21 at 22:56