Transition animation not working in SwiftUI

22,028

Solution 1

The problem is that when views come and go in a ZStack, their "zIndex" doesn't stay the same. What is happening is that the when "showMessage" goes from true to false, the VStack with the "Hello World" text is put at the bottom of the stack and the yellow color is immediately drawn over top of it. It is actually fading out but it's doing so behind the yellow color so you can't see it.

To fix it you need to explicitly specify the "zIndex" for each view in the stack so they always stay the same - like so:

struct ContentView: View {
@State private var showMessage = false

var body: some View {
    ZStack {
        Color.yellow.zIndex(0)

        VStack {
            Spacer()
            Button(action: {
                withAnimation(.easeOut(duration: 3)) {
                    self.showMessage.toggle()
                }
            }) {
                Text("SHOW MESSAGE")
            }
        }.zIndex(1)

        if showMessage {
            Text("HELLO WORLD!")
                .transition(.opacity)
                .zIndex(2)
        }
    }
}

}

Solution 2

My findings are that opacity transitions don't always work. (yet a slide in combination with an .animation will work..)

.transition(.opacity) //does not always work

If I write it as a custom animation it does work:

.transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2))) 
.zIndex(1)

Solution 3

I found a bug in swiftUI_preview for animations. when you use a transition animation in code and want to see that in SwiftUI_preview it will not show animations or just show when some view disappear with animation. for solving this problem you just need add your view in preview in a VStack. like this :

struct test_UI: View {
    @State var isShowSideBar = false
    var body: some View {
        ZStack {
            Button("ShowMenu") {
                withAnimation {
                    isShowSideBar.toggle()
                }
                
            }
            if isShowSideBar {
                SideBarView()
                    .transition(.slide)
            }
        }
    }
}
        struct SomeView_Previews: PreviewProvider {
        static var previews: some View {
            VStack {
               SomeView()
            }
        }
    }

after this all animations will be happen.

Solution 4

I believe this is a problem with the canvas. I was playing around with transitions this morning and while the don't work on the canvas, they DO seem to work in the simulator. Give that a try. I've reported the bug to Apple.

Solution 5

I like Scott Gribben's answer better (see below), but since I cannot delete this one (due to the green check), I'll just leave the original answer untouched. I would argue though, that I do consider it a bug. One would expect the zIndex to be implicitly assigned by the order views appear in code.


To work around it, you may embed the if statement inside a VStack.

struct ContentView: View {
    @State private var showMessage = false

    var body: some View {
        ZStack {
            Color.yellow

            VStack {
                Spacer()
                Button(action: {
                    withAnimation(.easeOut(duration: 3)) {
                        self.showMessage.toggle()
                    }
                }) {
                    Text("SHOW MESSAGE")
                }
            }

            VStack {
                if showMessage {
                    Text("HELLO WORLD!")
                        .transition(.opacity)
                }
            }
        }
    }
}
Share:
22,028
superpuccio
Author by

superpuccio

I’ve been a passionate iOS developer for more than six years. I started with Objective-C, then moved to Swift. SwiftUI/Combine early adopter. Author of https://github.com/matteopuc/swiftui-navigation-stack

Updated on December 14, 2021

Comments

  • superpuccio
    superpuccio over 2 years

    I'm trying to create a really simple transition animation that shows/hides a message in the center of the screen by tapping on a button:

    struct ContentView: View {
        @State private var showMessage = false
    
        var body: some View {
            ZStack {
                Color.yellow
    
                VStack {
                    Spacer()
                    Button(action: {
                        withAnimation(.easeOut(duration: 3)) {
                            self.showMessage.toggle()
                        }
                    }) {
                        Text("SHOW MESSAGE")
                    }
                }
    
                if showMessage {
                    Text("HELLO WORLD!")
                        .transition(.opacity)
                }
            }
        }
    }
    

    According to the documentation of the .transition(.opacity) animation

    A transition from transparent to opaque on insertion, and from opaque to transparent on removal.

    the message should fade in when the showMessage state property becomes true and fade out when it becomes false. This is not true in my case. The message shows up with a fade animation, but it hides with no animation at all. Any ideas?

    EDIT: See the result in the gif below taken from the simulator.

    enter image description here