swiftuivstacklazyvstack

LazyVStack scrolling stops animations


This code lists 100 rows with refresh icon animations. When using a LazyVStack instead of a VStack, after scrolling down to the bottom of this list all the animations stop, including those at the top of the list once you scroll back. No such issues with a VStack. Any ideas as to why this is happening and is there a workaround? Using Xcode 12.0.1, iOS 14, Swift 5

struct RefreshingList : View {
    @State var isAnimating = false

    var body: some View {
        ScrollView {
            LazyVStack { // <- change to VStack to have animations not stop
                ForEach(0..<100, id: \.self) { i in
                    HStack {
                        Text("\(i) -> ")
                        self.button()
                    }
                }
            }
        }
        .onAppear {
            self.isAnimating=true
        }
    }
    
    func button() -> some View {
        Button(action: {}, label: {
            Image(systemName: "arrow.2.circlepath")
                .rotationEffect(Angle(degrees: self.isAnimating ? 360.0 : 0.0))
                .animation(Animation.linear(duration: 2.0)
                            .repeatForever(autoreverses: false))
        })
    }
}

Solution

  • Animation happens when a change to a view on screen is observed. In the VStack all of the views are created immediately so SwiftUI is able to observe the change in rotation value for all views.

    In the LazyVStack case, the views are only created as needed, so the bottom ones aren't on screen when the rotation value changes in .onAppear. When they do appear on screen, it is with the already changed value, so no animation happens.

    One way to fix this is to let the buttons own the .isAnimating @State variable. Then anytime one is created, it will be animating.

    struct RefreshingList : View {
    
        var body: some View {
            ScrollView {
                LazyVStack {
                    ForEach(0..<100, id: \.self) { i in
                        HStack {
                            Text("\(i) -> ")
                            AnimatedButton()
                        }
                    }
                }
            }
        }
    }
    
    struct AnimatedButton: View {
        @State var isAnimating = false
        
        var body: some View {
            Button(action: {
                
            }, label: {
                Image(systemName: "arrow.2.circlepath")
                    .rotationEffect(Angle(degrees: self.isAnimating ? 360.0 : 0.0))
                    .animation(Animation.linear(duration: 2.0)
                                .repeatForever(autoreverses: false))
            })
            .onAppear {
                isAnimating = true
            }
        }
    }