48

I'm trying to make a list with the raw values of the cases from an enumeration with the new SwiftUI framework. However, I'm having a trouble with conforming the 'Data' to Identifiable protocol and I really cannot find information how to do it. It tells me "Initializer 'init(_:rowContent:)' requires that 'Data' conform to 'Identifiable'" The stub provides me with an ObjectIdentifier variable in the last extension, but don't know what should I return. Could you tell me how do it? How do I conform Data to Identifiable, so I can make a list with the raw values?

enum Data: String {
    case firstCase = "First string"
    case secondCase = "Second string"
    case thirdCase = "Third string"
}

extension Data: CaseIterable {
    static let randomSet = [Data.firstCase, Data.secondCase]
}

extension Data: Identifiable {
    var id: ObjectIdentifier {
        return //what?
    }

}

//-------------------------ContentView------------------------
import SwiftUI

struct Lala: View {
    var name: String

    var body: some View {
        Text(name)
    }
}

struct ContentView: View {
    var body: some View {
        return List(Data.allCases) { i in
            Lala(name: i.rawValue)
        }
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • 1
    *maybe* this will help : https://stackoverflow.com/questions/24011170/how-to-make-an-enum-conform-to-a-protocol-in-swift – user1105951 Aug 08 '19 at 09:59
  • `id` can be a string or basically anything `equatable`, so it depends what you want to achive. You could return the enum's raw string value if, for example, you want all `firstCase` instances to be identified as the same. To differentiate different instances of the same case you could use associated values to store some unique identifier & return that in the `id` computed property (an enum can't have stored properties, which is why we need to use an associated value) – Trev14 May 04 '22 at 15:49

5 Answers5

116

⚠️ Try not to use already used names like Data for your internal module. I will use MyEnum instead in this answer


When something conforms to Identifiable, it must return something that can be identified by that. So you should return something unique to that case. For String base enum, rawValue is the best option you have:

extension MyEnum: Identifiable {
    var id: RawValue { rawValue }
}

Also, enums can usually be identified by their selves:

extension MyEnum: Identifiable {
    var id: Self { self }
}

⚠️ Note 1: If you return something that is unstable, like UUID() or an index, this means you get a new object each time you get the object and this will kill reusability and can cause epic memory and layout process usage beside view management issues like transition management and etc.

Take a look at this weird animation for adding a new pet: UUID Example

Note 2: From Swift 5.1, single-line closures don't need the return keyword.

Note 3: Try not to use globally known names like Data for your own types. At least use namespace for that like MyCustomNameSpace.Data


Inline mode

You can make any collection iterable inline by one of it's element's keypath:

For example to self:

List(MyEnum.allCases, id:\.self)

or to any other compatible keypath:

List(MyEnum.allCases, id:\.rawValue)

✅ The checklist of the identifier: (from WWDC21)

  • Exercise caution with random identifiers.
  • Use stable identifiers.
  • Ensure the uniqueness, one identifier per item.
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
  • 1
    Could you elaborate on why a UUID will degrade performance? – Peter Kaminski Apr 22 '21 at 11:05
  • 1
    It will not! returning **A NEW** UUID each time will force the engine to build a brand new component instead of reusing the old one. – Mojtaba Hosseini Apr 22 '21 at 15:42
  • Got it, thanks for clarifying. Is there any documentation on how the id that's passed in is used by SwiftUI to determine if a view needs to be invalidated/created/recreated? – Peter Kaminski Apr 22 '21 at 15:45
  • The id must be unique **per** unique data. You just pass the data and let the render engine decide whether it needs an invalidated/created/recreated object. I think you should search around "List" and "identifier" and their documentations. – Mojtaba Hosseini Apr 22 '21 at 16:03
  • @PeterKaminski I know this didn't exist at the time you wrote this comment, but for anyone in the future as well - check out the demystifying SwiftUI WWDC21 session [here](https://developer.apple.com/videos/play/wwdc2021/10022/). – George Jul 30 '21 at 09:19
  • Isn't the issue in your UUID example that you made the id property a computed one instead of a stored? A computed property will create a new UUID every time it is accessed while a stored will not. How will the example look like if you change it to "var id = UUID()"? – PatrickDotStar Jul 25 '23 at 08:41
6

Another approach with associated values would be to do something like this, where all the associated values are identifiable.

enum DataEntryType: Identifiable {
    var id: String {
        switch self {
        case .thing1ThatIsIdentifiable(let thing1):
            return thing1.id
        case .thing2ThatIsIdentifiable(let thing2):
            return thing2.id
        }
    }
    
    case thing1ThatIsIdentifiable(AnIdentifiableObject)
    case thing2ThatIsIdentifiable(AnotherIdentifiableObject)
Dr. Mr. Uncle
  • 484
  • 5
  • 12
5

You can try this way:

enum MyEnum: Identifiable {
    case valu1, valu2
    
    var id: Int {
        get {
            hashValue
        }
    }
}
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
2

Based on a couple of answers above, this is an example where the enum it self is the id conforming the Identifiable.

enum Foo : Identifiable {
    var id: Foo {
        self
    }
    
    case one
    case two
}

And if you need it to make it for an enum with associated values, the trick is to also implement Hashable

enum Foo : Hashable, Identifiable {
    
    var id: Self {
        self
    }
    
    case one(foo: String)
    case two
}
cutiko
  • 9,887
  • 3
  • 45
  • 59
-5

Copyright © 2021 Mark Moeykens. All rights reserved. | @BigMtnStudio Combine Mastery in SwiftUI book

enum InvalidAgeError: String, Error , Identifiable {
    var id: String { rawValue }
    case lessThanZero = "Cannot be less than zero"
    case moreThanOneHundred = "Cannot be more than 100"
}
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178