My Animation in SwiftUI is not working properly

316 Views Asked by At

I have a problem with animation in SwiftUI, if I'm using .animation, my animation is not working.

ZStack(alignment: .bottom) {
    VStack {
        Button("Button") {
            showView.toggle()
        }
        Spacer()
    }
    
    if showView {
        RoundedRectangle(cornerRadius: 30)
            .frame(height: UIScreen.main.bounds.height * 0.5)
            .transition(.move(edge: .bottom))
            .animation(.easeInOut, value: showView)
    }
}
.ignoresSafeArea(edges: .bottom)

But if I'm using withAnimation in my button's action closure, and delete .animation or keep it, it is working.

ZStack(alignment: .bottom) {
    VStack {
        Button("Button") {
            withAnimation {
                showView.toggle()
            }
        }
        Spacer()
    }
    
    if showView {
        RoundedRectangle(cornerRadius: 30)
            .frame(height: UIScreen.main.bounds.height * 0.5)
            .transition(.move(edge: .bottom))
            .animation(.easeInOut, value: showView)
    }
}
.ignoresSafeArea(edges: .bottom)

Can someone explain me how this works ? I'm trying with DispatchQueue.main.async, it's still the same.

2

There are 2 best solutions below

7
son On BEST ANSWER

You're defining 2 different states:

  1. With RoundedRectangle if showView = true
  2. Without RoundedRectangle if showView = false

So, every time showView changes, the body will be re-rendered. If it toggles from false -> true, then RoundedRectangle will be displayed instantly without any animations because it's not here until showView = true. And if it toggles from true -> false, then it can be dismissed with animation because now, it's a part of the View. You may try this to make it show/disappear more smoothly:

ZStack(alignment: .bottom) {
    VStack {
        Button("Button") {
            showView.toggle()
        }

        Spacer()
    }

    RoundedRectangle(cornerRadius: 30)
        .frame(height: showView ? (UIScreen.main.bounds.height * 0.5) : 0)
        .transition(.move(edge: .bottom))
        .animation(.easeInOut, value: showView)
}
.ignoresSafeArea(edges: .bottom)
5
JanMensch On

Put .animation(..) on the outer containing view like this:

ZStack(alignment: .bottom) {
    Button("Button") {
        showView.toggle()
    }
        
    if showView {
        RoundedRectangle(cornerRadius: 30)
            ...
    }
}
.ignoresSafeArea(edges: .bottom)
.animation(.easeInOut, value: showView) // <-- on ZStack, not on Rectangle

The reason this fixes the issue is that now SwiftUI always knows, that changes of showView need animations.

In your code, the Rectangle is completely removed from the view hierarchy when showView is false. Meaning: it does not exist for SwiftUI and thus it also doesn't know that there is an animation.

Once the flag changes to true, SwiftUI will just do a normal rerender, will see a change in the if statement and will now render the Rectangle. And then, the animation is set. After the view was defined. So SwiftUI will know that now changes of showView need to be animated. But by that time it's too late to animate the showing.

Moving the .animation out of the if means, that the animation is defined in a place that always exists, no matter what state the view is in. So when the flag changes, SwiftUI rerenders and knows that any differences in the UI caused by this change, need to be animated.

Son's answer works for the same reason, but uses a very different approach. In his answer, SwiftUI always shows the Rectangle. Meaning SwiftUI always knows about it and about the animation that is defined on it. In his answer he just reduces the height of the view to 0 if it should be hidden. So the view is still there, it's just that the user can't see it. Once the flag changes, the view doesn't technically get shown, it just animates it's change in height. Doing that might just cause other UI glitches, depending on what you show in the view that gets shown/hidden. It might happen that during the animation the content gets "shoved around", squashed/stretched etc.