Experimenting with SwiftUI animations and it's taxing my old grey matter. I'm getting different results for two very similar variants of code, and can't work out why.
Example: The below produces a vertical stack of three images.
@State private var rotationAmount = 0.0
VStack{
ForEach(0..<3) { number in
Button {
rotationAmount = 360
//execute main functionality
} label: {
MyImage(number)
.rotationEffect(.degrees(rotationAmount), anchor: .center)
}
}
}
When one of the images (i.e. buttons) is clicked its image spins 360 deg on its Z axis
However if the button action code is changed so it uses a withAnimation
block the behaviour is different:
Button{
withAnimation {
rotationAmount = 360
}
//execute main functionality
} label: {
MyImage(number)
.rotationEffect(.degrees(rotationAmount), anchor: .center)
}
With this variation clicking any of the images results in all three of them spinning.
In both cases every button has an animation bound to the rotationAmount
property, so I don't understand the difference in behaviour between the two examples. If anything I'd expect all three images to spin in both situations as they are all bounds to the same mutating property.
CodePudding user response:
Each time rotationAmount
changes it causes the ContentView
s body to get reevaluated. Due to 360
being added to rotationAmount
each time, we wouldn't expect there to be any visible changes when there's no animation as the view would be rotated around to the exact position it's in.
To see this you could replace .rotationEffect(.degrees(rotationAmount), anchor: .center)
with .font(rotationAmount == 0.0 ? .body : .largeTitle)
, this will show you that all the views get updated, just some without an animation.
Button {
rotationAmount = 360
} label: {
MyImage(number)
.font(rotationAmount == 0.0 ? .body : .largeTitle)
}
The behavior of Button
when being tapped is what is causing one image to rotate with animation without explicitly telling it to do so. Due to the highlight state of the button causing animated changes to the label of the button, animations are also applied to other changes that occur to the button at the same time.
To see that it's caused by the image being in a button label specifically you could use .onTapGesture
to see that it won't update when it's not in the button label:
MyImage(number)
.rotationEffect(.degrees(rotationAmount), anchor: .center)
.onTapGesture {
rotationAmount = 360
}
You can also see that it's caused by other animations occurring to the button label at the same time, the code below will cause the rotationEffect
change to occur once the button highlighted state has finished animating, resulting in the image not animating when the property changes:
Button {
DispatchQueue.main.asyncAfter(deadline: .now() 1, execute: {
rotationAmount = 360
})
} label: {
MyImage(number)
.rotationEffect(.degrees(rotationAmount), anchor: .center)
}
Here's an example shows that using withAnimation
to update one property and not placing the other property change inside withAnimation
will cause only the property in the withAnimation
block to animate when the button is tapped:
Button {
rotationAmount = 360
withAnimation {
myColorEnabled.toggle() // @State var myColorEnabled = false
}
} label: {
Text("Test")
.rotationEffect(.degrees(rotationAmount), anchor: .center)
Text("Hello")
.foregroundColor(myColorEnabled ? .red : .gray)
}