iosswiftswiftuiios16

Weird animation of a Button in Stack Views in iOS 16


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:

enter image description here

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:

enter image description here

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?


Solution

  • 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.

    Showing bug in simulator

    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 { }.