Investigations
Let's start with a basic ForEach
:
struct ContentView: View {
var body: some View {
ForEach(0 ..< 5) { index in
Text("Index: \(index)")
}
}
}
This works perfectly fine, as you'd expect. The range is constant and never changes so a unique identifier is not needed. The identity of the views won't change so it the id
parameter is not required.
What if the range may change? Xcode warns you (as of later versions):
struct ContentView: View {
@State private var range = 0 ..< 5
var body: some View {
ForEach(range) { index in // ⚠️ Non-constant range: not an integer range
Text("Index: \(index)")
}
}
}
This warning indicates that if range
changes and therefore the data for the ForEach
changes, the identity of the views may change.
Now we can investigate using the id
parameter for ForEach
versus adding an .id(...)
modifier to the view inside. Here is a basic example:
struct Item: Identifiable {
let id: Int
let value: Int
}
struct ContentView: View {
@State private var data = [
Item(id: 0, value: 1),
Item(id: 1, value: 2),
Item(id: 1, value: 3)
]
var body: some View {
ForEach(data, id: \.id) { item in
Text("Value: \(item.value)")
.id(item.value)
}
}
}
The \.id
key path in the above code isn't necessary since Item
conforms to Identifiable
, so it's only there for explicitness of the example. You can replace the id: \.id
with id: \.value
to see the effect of the ID here:
Using \.id |
Using \.value |
 |
 |
As you'll notice, the .id(...)
in the body had no effect on the ID the ForEach
uses - the ForEach
only cares about what ID you give it with the ID key path. However, using the .id(...)
inappropriately may still break animations and such.
Answers to your questions
Question 1
Does the use of id: \.self
in this case affect things negatively/does it necessarily lead to possibly inconsistent behavior?
The answer here is it depends but most likely yes. If the value of config.rows
isn't changing then you'd be completely fine. However, if the value of config.rows
changes, then the view identities will have changed.
Question 2
More specifically, does the id: \.self
become the identity of the SomeOtherView
or not?
The identity of the view inside the ForEach
is separate from the identity ForEach
uses when rendering the views, as demonstrated in the investigations above.
Question 3
If yes to 1 or 2 above—and, if using id: \.self
should thus be avoided at all cost—is there some low-friction solution canonical fix one should reach for first?
It depends on your data:
Kind of data |
Solution |
Constant range |
No id needed. |
Variable range |
Usually identifying by \.self is sufficient for simple cases, but if not, most likely you don't want a range here - but rather to pass in the actual data. |
Collection of data |
Best uniquely identifiable value for the data. Pretend it's an ID for a database - you don't want any collisions so it must be unique. Usually, your data model would contain some unique ID - commonly the Identifiable protocol is used to remove the need for explicitly specifying the ID key path in the ForEach and UUID is usually the easiest way to do so. |
You must be careful what data you give as your unique ID. For example, if we stored all students in a classroom with a unique ID from 1 to N where N is the number of students, this may seem fine at first. But imagine we remove a student from the class and then later add a new student in their place with the same ID as earlier. This would be incorrect - these students are unique and so should not share the same ID - even if not co-existing at the same time.
Why does it matter if view identities change?
The view identity changes matter. From the ForEach
documentation:
It’s important that the id of a data element doesn’t change, unless SwiftUI considers the data element to have been replaced with a new data element that has a new identity. If the id of a data element changes, then the content view generated from that data element will lose any current state and animations.
Issues caused:
- Animations break as a view cannot animate from a previous state since the identity has changed.
- Worse rendering performance since SwiftUI believes the view is different for the same reason as above.
Recommended watching
I highly recommend watching the WWDC21 video Demystify SwiftUI to understand more about view identity and lifecycle in detail.