Ben Scheirman
Ben Scheirman

Reputation: 40981

SwiftUI - Animating Multiple Parameters with Different Curves

I'm exploring a sine wave rendering in a SwiftUI View, and have built my view to have 3 controllable parameters:

Here's a screenshot:

enter image description here

My SineWave is implemented as a Shape, so it is already Animatable. I made amplitude and frequency an AnimatablePair and expose it as my shape's animatable data like this:

var animatableData: AnimatablePair<CGFloat, CGFloat> {
    get { .init(frequency, amplitude) }
    set {
        frequency = newValue.first
        amplitude = newValue.second
    }
}

This works, and I get a nice animation if I do this in my containing view:

    SineWaveShape(phase: phase, amplitude: amplitude, frequency: frequency)
        .stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
        .foregroundColor(.red)
        .onAppear {
            withAnimation(Animation.spring(response: 0.5, dampingFraction: 0.4, blendDuration: 0.1).repeatForever()) {
                amplitude = 0.1
                frequency = 4
            }
        }

Now I want to animate the phase as well. But I don't want this one to auto-reverse, and I want it to be much faster. Unfortunately adding another withAnimation block next to this one has no effect, even if I have it as part of my animatable data. The last animation block always wins.

How should I approach this problem of wanting to animate two properties with two different Animation instances?

Upvotes: 4

Views: 1385

Answers (1)

Asperi
Asperi

Reputation: 258117

Here is possible approach (sketch). Assuming you combine your animatable pair into struct, to work with them as single value, then you can use .animation(_, value:) to specify dedicated animation for each value:

@State private var pair: CGSize = .zero
@State private var phase: CGFloat = .zero

...

SineWaveShape(phase: phase, amplitude: amplitude, frequency: frequency)
    .stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
    .foregroundColor(.red)
    .animation(Animation.linear(duration: 5).repeatForever(), value: phase)
    .animation(Animation.spring(response: 0.5, dampingFraction: 0.4, blendDuration: 0.1).repeatForever(), value: pair)
    .onAppear {
        self.phase = 90
        self.pair = CGSize(width: 0.1, height: 4)
    }
                

Upvotes: 3

Related Questions