LazyVStack scrolling stops animations

2.7k views Asked by At

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))
        })
    }
}
2

There are 2 answers

0
vacawama On BEST ANSWER

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
        }
    }
}
1
Dim Novo On
struct RefreshingListView: View {
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(0..<100) {
                    LabelView(id: $0)
                        .id($0)
                }
            }
            .animation(
                Animation
                    .linear(
                        duration: 2)
                    .repeatForever(
                        autoreverses: false))

        }
    }
}

struct LabelView: View {
    @State var
        animate = false
    var id: Int
    var body: some View {
        Label(
            title: {
                Text("<- \(id)")
            }, icon: {
                Image(
                    systemName:
                        "arrow.2.circlepath")
                    .foregroundColor(.blue)
                    .padding(.horizontal)
                    .rotationEffect(
                        Angle(
                            degrees:
                                animate ?
                                360 : 0))
            }
        )
        .onAppear {
            animate
                .toggle()
        }
    }
}