I'm exploring a sine wave rendering in a SwiftUI View, and have built my view to have 3 controllable parameters:
phase (to control the offset and allow animating "horizontally")amplitude (to control how high each peak is)frequency (how many complete waves there are)Here's a screenshot:

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?
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)
    }
                
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With