Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI inverse animation delay on removal

Tags:

swift

swiftui

I am trying to choreograph animations in SwiftUI. I want two colored bars to move in to the view with a delay between them and then move out of the view with the delays switched so that the removal is the reverse of the insertion.

I think I understand why the below code doesn't work, but I can't figure out how to make it do what I need:

import SwiftUI

struct TestAnimControl: View {
    @State var show: Bool = false
    @State var reverseDelay: Bool = false

    var body: some View {
        VStack {
            Button(action:{
                self.show.toggle()
            }) {
                Text("Animate")
                    .font(.largeTitle)
            }
            if show {
                Rectangle()
                    .foregroundColor(.blue)
                    .frame(height: 100)
                    .transition(.move(edge: .trailing))
                    .animation(Animation.spring().delay(show ? 0.3 : 0.5))
                Rectangle()
                    .foregroundColor(.red)
                    .frame(height: 100)
                    .transition(.move(edge: .trailing))
                    .animation(Animation.spring().delay(show ? 0.5 : 0.3))
            }
        }
    }
}

When you run this and hit the button, the blue bar moves in and then the red bar moves in. Hit the button again and the blue bar moves out and then the red bar moves out. What I want is when you hit the button for removal, the red bar moves out and then the blue bar moves out, reverse of the way the bars came in. In this code I believe the ternary doesn't work because the animation is set when the Rectangle is created and the delay can't change after that. I may be wrong, but either way is there a way to do what I am trying to do?

like image 661
twesque Avatar asked Oct 21 '25 07:10

twesque


1 Answers

Update: Xcode 13.4 / iOS 15.5

A proposed solution now is based on explicit animation with modification in every transaction so each transition have own parametrised variant of animation.

demo

Main part:

Button(action:{
    withAnimation {
        self.show.toggle()
    }
}) {
    Text("Animate")
        .font(.largeTitle)
}
if show {
    Rectangle()
        .foregroundColor(.blue)
        .frame(height: 100)
        .transition(.move(edge: .trailing))
        .transaction {
            $0.animation = Animation.spring().delay(delay1)
        }
        .onAppear { self.delay1 = 0.5 }
        .onDisappear { self.delay1 = 0.3 }
    Rectangle()
        .foregroundColor(.yellow)
        .frame(height: 100)
        .transition(.move(edge: .trailing))
        .transaction {
            $0.animation = Animation.spring().delay(delay2)
        }
        .onAppear { self.delay2 = 0.3 }
        .onDisappear { self.delay2 = 0.5 }
}

Test code on GitHub

Original:

!!! It does not work anymore with modern OS

Here is a solution - based on applied implicit animations to every transition. Tested with Xcode 11.4 / iOS 13.4

struct TestAnimControl: View {
    @State var show: Bool = false
    @State var reverseDelay: Bool = false

    @State var blueDelay = 0.3
    @State var redDelay = 0.5
    var body: some View {
        VStack {
            Button(action:{
                self.show.toggle()
            }) {
                Text("Animate")
                    .font(.largeTitle)
            }
            if show {
                Rectangle()
                    .foregroundColor(.blue)
                    .frame(height: 100)
                    .transition(.move(edge: .trailing))
                    .animation(Animation.spring().delay(blueDelay))//(show ? 0.3 : 0.5))
                    .onAppear { self.blueDelay = 0.5 }
                    .onDisappear { self.blueDelay = 0.3 }
                Rectangle()
                    .foregroundColor(.red)
                    .frame(height: 100)
                    .transition(.move(edge: .trailing))
                    .animation(Animation.spring().delay(redDelay))//(show ? 0.5 : 0.3))
                    .onAppear { self.redDelay = 0.3 }
                    .onDisappear { self.redDelay = 0.5 }
            }
        }
    }
}
like image 82
Asperi Avatar answered Oct 24 '25 09:10

Asperi