Focusing on comprehending the underlying concept of the various animation behaviors, I am interested of programmatically animate the change of a row in a List.
I am able to achieve it in the following example:
struct First: View {
@State var items = ["One", "Two", "Three", "Four"]
var body: some View {
VStack {
List(items, id: \.self) { item in
Text(item)
}
Button("Hit me") {
withAnimation {
items.swapAt(2, 0)
}
}
}
}
}
As you can see upon tapping on the button, we receive a nice swapping animation in the cells. We can see that cells are actually moving, one to the top and the other downwards:
When I change the list code to the following:
List(items.indices, id: \.self) { index in
Text(items[index])
}
As you can see the prior animation doesn't work anymore and instead I receive a text change animation:
Is there a solution to keep the first (default) swapping animation ?
Thanks.
It's a question of identity. SwiftUI keeps track of view elements by their identity, and it can only animate between states when the identities of the single elements stay the same.
In the first code, the List id is of type String
, the string changes place but keeps the same id.
In the second code the List id is of type Index, but when swapping the index does not change, only the relative string at that index.
So in result 1. animates the position based on string id, and 2. animates the string based on index id.
BUT you can tell SwiftUI explicitly which id to use, also in the second code, like this:
List(items.indices, id: \.self) { index in
Text(items[index]).id(items[index]) // Here
}
You can't use indices with the ForEach
, it's a View
not a for loop. The first problem is the animations as you saw, the next problem is it will crash when you remove items.
When your list items are an array of value types in an @State
you need to pass a valid id
param, e.g.
List(items, id: \.uniqueIdentifier) { item in
The reason is so the List
can track the inserts, removes and deletes. Value types don't have inherent identity like objects do because they are copied everywhere rather than a reference being copied.
Most SwiftUI Views that work on arrays like List
make use of the Identifiable
protocol to make this simpler, e.g.
struct Item: Identifiable {
let id = UUID() // this could also be a computed var that concatenates a bunch of properties that result in a unique string for this Item.
let text: String
}
struct First: View {
@State var items = [Item(text: "One"), Item(text: "Two"), Item(text: "Three"), Item(text: "Four")]
var body: some View {
VStack {
List(items) { item in // no id param needed when the item conforms to Identifiable
Text(item.text)
}
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With