Reputation: 40981
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?
Upvotes: 4
Views: 1385
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