Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI animation and subsequent reverse animation to original state

I'm using SwiftUI and I want to animate a view as soon as it appears (the explicit type of animation does not matter) for demo purposes in my app. Let's say I just want to scale up my view and then scale it down to its original size again, I need to be able to animate the view to a new state and back to the original state right afterward. Here's the sample code of what I've tried so far:

import SwiftUI
import Combine

struct ContentView: View {
    @State private var shouldAnimate = false
    private var scalingFactor: CGFloat = 2    

    var body: some View {
        Text("hello world")
        .scaleEffect(self.shouldAnimate ? self.scalingFactor : 1)
        .onAppear {
            let animation = Animation.spring().repeatCount(1, autoreverses: true)
            withAnimation(animation) {
                self.shouldAnimate.toggle()
            }
        }
    }

Obviously this does not quite fulfill my requirements, because let animation = Animation.spring().repeatCount(1, autoreverses: true) only makes sure the animation (to the new state) is being repeated by using a smooth autoreverse = true setting, which still leads to a final state with the view being scaled to scalingFactor.

So neither can I find any property on the animation which lets my reverse my animation back to the original state automatically (without me having to interact with the view after the first animation), nor did I find anything on how to determine when the first animation has actually finished, in order to be able to trigger a new animation.

I find it pretty common practice to animate some View upon its appearance, e.g. just to showcase that this view can be interacted with, but ultimately not alter the state of the view. For example animate a bounce effect on a button, which in the end sets the button back to its original state. Of course I found several solutions suggesting to interact with the button to trigger a reverse animation back to its original state, but that's not what I'm looking for.

like image 402
Alienbash Avatar asked May 23 '20 12:05

Alienbash


2 Answers

Here is a solution based on ReversingScale animatable modifier, from this my answer

Update: Xcode 13.4 / iOS 15.5

demo

Complete test module is here

Tested with Xcode 11.4 / iOS 13.4

demo

struct DemoReverseAnimation: View {
    @State var scalingFactor: CGFloat = 1

    var body: some View {
        Text("hello world")
        .modifier(ReversingScale(to: scalingFactor, onEnded: {
            DispatchQueue.main.async {
                self.scalingFactor = 1
            }
        }))
        .animation(.default)
        .onAppear {
            self.scalingFactor = 2
        }
    }
}
like image 176
Asperi Avatar answered Oct 01 '22 08:10

Asperi


Another approach which works if you define how long the animation should take:

struct ContentView: View {
    @State private var shouldAnimate = false
    private var scalingFactor: CGFloat = 2

    var body: some View {
        Text("hello world")
            .scaleEffect(self.shouldAnimate ? self.scalingFactor : 1)
            .onAppear {
                let animation = Animation.easeInOut(duration: 2).repeatCount(1, autoreverses: true)
                withAnimation(animation) {
                    self.shouldAnimate.toggle()
                }
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    withAnimation(animation) {
                        self.shouldAnimate.toggle()
                    }
                }
        }
    }
}
like image 37
Kuhlemann Avatar answered Oct 01 '22 10:10

Kuhlemann