Animate Change in SwiftUI ForEach Grid Using Array Indices

Viewed 273

Does anyone know how I can animate a sorting change to grid items while iterating off of the array index in a ForEach?

I am showing a grid of items (LazyVGrid) from an array and the user can change the sort order of these items. When the sort order changes, I'd like to animate the change in the grid. This would work great if I were to use code similar to the following:

            Button {
                withAnimation {
                    noteModel.sortMethod = noteModel.sortMethod.next()
                }
            } label: {
                Text("Change Sort Order")
            }

            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(self.notes, id: \.self) { note in
                        VStack {
                            Text(note.title)
                            Text(note.body)
                        }
                        .padding()
                    }
                }
            }

However, if my ForEach iterates off of the array index the change does not animate. So it won't animate if my code is similar to this:

            Button {
                withAnimation {
                    noteModel.sortMethod = noteModel.sortMethod.next()
                }
            } label: {
                Text("Change Sort Order")
            }

            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(self.notes.indices, id: \.self) { idx in
                        VStack {
                            Text(self.notes[idx].title)
                            Text(self.notes[idx].body)
                        }
                        .padding()
                    }
                }
            }

Why don't I just do it the first way? The reason is because I found a great resource for dynamically adjusting frame sizes for items inside a grid (found here: https://swiftui-lab.com/impossible-grids/), but that requires the use of array indices in order to work. It's pretty cool and I am hoping I don't have to choose one or the other (dynamic sizing or animate changes).

Thoughts or ideas would be greatly appreciated. Thanks!

1 Answers

ForEach in SwiftUI isn’t the same as a for loop. Your data needs to be identifiable for structural identity to work and you certainly should not be using indices. Either implement the Identifiable protocol or tell ForEach what key path is a unique ID (or is a getter that creates one from other properties) in the item struct. Don’t ever use id:\.self.

You can learn more about structural identity in the video WWDC 2021 Demystify SwiftUI

Related