swiftui

Is there a way to easily represent in-place animations that end where they started in SwiftUI?


I know there are a few frameworks out there that do this through more complicated means. But I am curious whether there is a way to do this in general, which is easier to set up.

In my case, I am trying to replicate .symbolEffect(.bounce, value: bounce) but for an arbitrary view.

The problem with this is that normally if I wanted that, I would have a modifier on my view like .scaleEffect(bounce ? 1 : 1.2) and then perhaps some sort of spring animation like .animation(.interpolatingSpring(stiffness: 350, damping: 5, initialVelocity: 10), value: bounce).

The problem, of course, is that this animation does not end where it started. So, at the end of the animation, I would end up with the scale being 1.2, which was not the goal. I could, of course, create a Task that, after a delay, sets bounce back to false, but that would trigger a spring animation back to 1.0.

Even if I did balance everything correctly, the animation would not look nearly as good as .symbolEffect(.bounce, value: bounce)

Is there any sort of repeatable way to have a spring animation end where it started?


Solution

  • phaseAnimator does exactly that - it resets to the initial phase after all phases have been animated.

    You can set up two phases - one for scaleEffect(1) and one for scaleEffect(1.2)

    It also takes an animation: parameter, which allows you to specify an Animation to use for each phase. In the below example, I used .spring.speed(2) for both phases.

    @State private var trigger = false
    
    var body: some View {
        Circle().frame(width: 100)
            .phaseAnimator([false, true], trigger: trigger) { view, phase in
                view.scaleEffect(phase ? 1.2 : 1)
            } animation: { _ in .spring.speed(2) }
        Button("Trigger") {
            trigger.toggle()
        }
    }