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