Learn how Wave seamlessly transitions between animation targets while preserving velocity and momentum
Retargeting is Wave’s ability to smoothly change an animation’s target value while it’s already running. This creates fluid, interruptible animations that feel natural and responsive.
When you modify a SpringAnimator’s target property during animation, Wave handles the transition seamlessly:
// From SpringAnimator.swift:64-81public var target: T.ValueType? { didSet { guard let oldValue = oldValue, let newValue = target else { return } if oldValue == newValue { return } if state == .running { // Reset start time to begin fresh spring calculation self.startTime = CACurrentMediaTime() let event = Event.retargeted(from: oldValue, to: newValue) completion?(event) } }}
The key insight: Wave resets the startTime but preserves the current value and velocity. This ensures smooth transitions without velocity discontinuities.
When using SpringAnimator directly, you get more detailed event information:
let animator = SpringAnimator<CGPoint>(spring: spring, value: .zero, target: endPoint)animator.completion = { event in switch event { case .finished(let finalValue): print("Finished at: \(finalValue)") case .retargeted(let oldTarget, let newTarget): print("Retargeted from \(oldTarget) to \(newTarget)") }}animator.start()
The .retargeted event is defined in SpringAnimator.swift:12-25 and provides both the old and new target values.
Retargeting shines in interactive scenarios. Here’s how the Picture-in-Picture sample uses it:
// From PictureInPictureViewController.swift:84-98switch gesture.state {case .began, .changed: let targetCenter = CGPoint( x: initialLocation.x + translation.x, y: initialLocation.y + translation.y ) // Fast spring for interactive tracking // Each pan gesture update retargets the animation Wave.animate(withSpring: interactiveSpring) { pipView.animator.center = targetCenter }case .ended: let destination = calculateDestination(velocity: touchVelocity) // Retarget to final position with looser spring // Velocity is preserved from the interactive animation! Wave.animate(withSpring: animatedSpring, gestureVelocity: touchVelocity) { pipView.animator.center = destination }}
Notice how:
During dragging (.changed), the target continuously updates as the user moves their finger
Each update retargets the animation, creating smooth tracking
On release (.ended), it retargets one final time to the destination
The gesture velocity is injected to maintain momentum
One of retargeting’s most powerful features is velocity preservation. When you retarget an animation, the current velocity continues into the new animation:
let animator = SpringAnimator<CGFloat>(spring: spring, value: 0, target: 100)animator.start()// Later, while the animation is running...// Current velocity might be 150 units/secondanimator.target = 200 // Retarget!// Velocity of 150 units/second is preserved
This is why Wave animations feel so natural - there are no sudden velocity changes or jarring transitions.
Technical implementation details
From SpringAnimator.swift:203-247, the updateAnimation method shows how velocity is preserved:
func updateAnimation(dt: TimeInterval) { guard let value = value, let target = target else { return } self.state = .running let isAnimated = spring.response > .zero && mode != .nonAnimated if isAnimated { // Calculate new value AND velocity based on spring physics (newValue, newVelocity) = T.updateValue( spring: spring, value: value, target: target, velocity: velocity, // Current velocity is used dt: dt ) } self.value = newValue self.velocity = newVelocity // Updated for next frame}
When the target changes, startTime resets but velocity persists, ensuring smooth transitions.
Wave handles rapid retargeting gracefully. Each new target update:
Cancels the previous target
Preserves the current value and velocity
Begins animating toward the new target
// Rapid-fire retargeting (like tracking a finger)for position in fingerPositions { Wave.animate(withSpring: spring) { view.animator.center = position }}// Wave smoothly tracks through all positions
Retargeting only works when an animation is in the .running state. If the animation hasn’t started or has already ended, setting a new target starts a fresh animation instead.