The Identifiable protocol in SwiftUI allows you to add a unique object identity. The protocol requires a single ID property of any hashable type, making it a flexible protocol for all kinds of instances.
While it’s a relatively simple protocol, a few possible edge cases can cause unexpected bugs in your SwiftUI code. Therefore, it’s important to understand how to use the protocol correctly.
Conforming your object to Identifiable
When creating new instances in Swift, you’re likely not considering adding Identifiable protocol conformance immediately. It’s more common to run into SwiftUI-related build errors like:
Referencing initializer ‘init(_:content:)’ on ‘ForEach’ requires that ‘Article’ conform to ‘Identifiable’
This build error occurs when you’re using a non-identifiable collection of objects inside a ForEach element:
SwiftUI requires an identity for each object since it would otherwise not be able to determine whether it’s necessary to redraw a view after the collection changes. In other words, adding identity impacts the behavior of your views.
We can demonstrate this by adding Identifiable protocol conformance to the article structure:
struct Article {
let title: String
let url: URL
}
extension Article: Identifiable {
var id: String {
url.absoluteString
}
}
At first glance, this code seems perfectly fine as we can successfully compile and run our project. However, as soon as we encounter two articles with the same URL, the resulting output becomes unexpected:
While both articles have a unique title, the URL remains the same. The output view shows the same title twice since it thinks both articles are identical based on the identity. This demonstrates why you should carefully add identity to objects and consider whether the ID used is unique in all cases.
In this example, we can properly add identity support by introducing a new unique post ID property:
struct Article {
let postID: Int
let title: String
let url: URL
}
extension Article: Identifiable {
var id: Int {
postID
}
}
Relying on the default implementation for classes
You might have noticed you’re not required to add any ID property to classes to which you’ve added Identifiable conformance. The Identifiable protocol provides a default implementation for class types, which are only guaranteed to remain unique for the lifetime of an object.
The default ID type will be ObjectIdentifier
, a unique identifier for a class instance or metatype. We can demonstrates this behavior by creating a class version of our article:
class ArticleClass: Identifiable {
let postID: Int
let title: String
let url: URL
init(postID: Int, title: String, url: URL) {
self.postID = postID
self.title = title
self.url = url
}
}
There’s no requirement to add a new ID property and printing out the default implementation shows something like:
print("\(articleClass.id)")
/// Prints: ObjectIdentifier(0x0000600000be0810)
The ID equals the object pointer, so it’s only guaranteed to be unique during the object’s lifetime.
Note: it’s essential to realize you should use a struct for the articles in this example. It might look like a solution to change all your types to classes, but this is wrong. You can learn more about this in my article Struct vs classes in Swift: The differences explained.
Conclusion
The Identifiable protocol allows you to iterate over a collection of objects inside a ForEach SwiftUI element. While it looks like a simple protocol with just a single property, it’s easy to make a common mistake that could lead to unexpected behavior in your view. Classes can rely on a default implementation, but you should not change all your structs to classes just to make them conform to Identifiable.
If you want to improve your SwiftUI knowledge, even more, check out the SwiftUI category page. Feel free to contact me or tweet me on Twitter if you have any additional tips or feedback.
Thanks!