Home > Software design >  SwiftUI ForEach: laggy animation
SwiftUI ForEach: laggy animation

Time:12-06

I have a view where I need to shuffle/sort the content shown in a ForEach 'list'. When manipulating the array, the animation is somehow everything else but seamless. The animation fails in 8 out of 10 cases and I don't understand why.
I've included a simplified version of my code with the same odd behaviour and a screen recording below.
Am I missing something or is this a swiftUI bug?

struct TestView: View {
    
    @State private var array: [Element] = [
        Element(txt: "A"),
        Element(txt: "B"),
        Element(txt: "C"),
        Element(txt: "D"),
        Element(txt: "E"),
        Element(txt: "F"),
        Element(txt: "G"),
    ]
    
    var body: some View {
        VStack {
            Text(verbatim: "Shuffle Animation")
                .font(.title2)
                .padding(.bottom, 50)
            
            ForEach(array) { element in
                VStack {
                    Text(verbatim: element.txt)
                        .padding()
                }
                .foregroundColor(.white)
                .frame(maxWidth: .infinity)
                .background(.orange)
                .cornerRadius(8)
                .contextMenu {
                    Button("shuffle", action: {
                        withAnimation {
                            array.shuffle()
                        }
                    })
                }
            }
            .padding(.horizontal)
        }
    }
}

struct Element: Identifiable, Hashable {
    var id: UUID = UUID()
    var txt: String
}


Screen recording:

enter image description here

CodePudding user response:

Two things:

  1. Swift does fail to animate buttons that are being interacted with. You can work around this by using DispatchQueue.main.asyncAfter to introduce a slight delay before animating the shuffle.
  2. Add an id in ForEach so that each letter animates from its old location to its new location.

ForEach(array, id: \.self) { element in
    VStack {
        Text(verbatim: element.txt)
            .padding()
    }
    .foregroundColor(.white)
    .frame(maxWidth: .infinity)
    .background(.orange)
    .cornerRadius(8)
    .contextMenu {
        Button("shuffle", action: {
            DispatchQueue.main.asyncAfter(deadline: .now()   0.3) {
                withAnimation {
                    array.shuffle()
                }
            }
        })
        
    }
}
.padding(.horizontal)
  • Related