2

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
    It is not animated by indexes, because indexes are not changed after sorting (1,2,3... stay 1,2,3...), so nothing to animate. – Asperi Dec 24 '21 at 16:49
  • This needs a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). – Yrb Dec 24 '21 at 17:05
  • Thank you Asperi! That makes complete sense now. I appreciate your help! – Craig Swanson Dec 24 '21 at 18:14

1 Answers1

0

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

malhal
  • 26,330
  • 7
  • 115
  • 133
  • This, along with the comment from Asperi above, give some clarity to my issue. Thanks for the replies. – Craig Swanson Dec 27 '21 at 14:55
  • Adding a follow-up because with this understanding I was able to find a post that discussed the topic in some detail. It also helped me to discover a way to have access to an index and animate the array. The post is here: https://stackoverflow.com/questions/57244713/get-index-in-foreach-in-swiftui – Craig Swanson Dec 27 '21 at 15:28