I created a SampleView
where a Button
is wrapped in a ZStack
, and the whole ZStack
is animated on tap of the Button
. It looks like below:
struct SampleView: View {
@State private var toggle: Bool = false
var body: some View {
ZStack {
Button {
toggle.toggle()
} label: {
Text("Tap here to move!")
.font(.system(size: 20, weight: .black))
.foregroundColor(.black)
}
.padding(10)
.background(.red)
}
.offset(x: 0, y: toggle ? 0 : 200)
.animation(.easeInOut(duration: 1), value: toggle)
}
}
struct SampleView_Previews: PreviewProvider {
static var previews: some View {
// ZStack {
SampleView()
// }
}
}
There's a bunch of other views that I want to present, so I simply wrapped SampleView
inside a VStack
(or ZStack
). Now, the animation started to break:
struct SampleView_Previews: PreviewProvider {
static var previews: some View {
ZStack {
SampleView()
}
}
}
I noticed that I can work around this behavior by wrapping the button action with withAnimation
.
withAnimation(.easeInOut(duration: 1)) {
toggle.toggle()
}
Still, I wonder what's going on here. Is this a bug?
This certainly looks like a bug. The Text
label of the button is not getting the .animation()
value applied to it, but the background is.
Here is another way to fix it. Apply the .animation()
modifier to the Text
label as well:
struct SampleView: View {
@State private var toggle: Bool = false
var body: some View {
ZStack {
Button {
toggle.toggle()
} label: {
Text("Tap here to move!")
.font(.system(size: 20, weight: .black))
.foregroundColor(.black)
.animation(.easeInOut(duration: 1), value: toggle) // here
}
.padding(10)
.background(.red)
}
.offset(x: 0, y: toggle ? 0 : 200)
.animation(.easeInOut(duration: 1), value: toggle)
}
}
More Evidence of the Bug
In this example, we have two buttons in a VStack
that move together. This works correctly in iOS 15.5, but it breaks in iOS 16. The animation breaks for the button that is being pressed, but it works for the other button.
struct ContentView: View {
@State private var toggle: Bool = false
var body: some View {
VStack {
Button {
toggle.toggle()
} label: {
Text("Tap here to move!")
.font(.system(size: 20, weight: .black))
.foregroundColor(.black)
}
.padding(10)
.background(.red)
Button {
toggle.toggle()
} label: {
Text("Tap here to move!")
.font(.system(size: 20, weight: .black))
.foregroundColor(.black)
}
.padding(10)
.background(.blue)
}
.offset(x: 0, y: toggle ? 0 : 200)
.animation(.easeInOut(duration: 1), value: toggle)
}
}
The same fixes apply here: add the .animation()
modifier to the Text
label of each button, or wrap the button action with withAnimation { }
.