Create your first Wave animation and learn the fundamentals in under 5 minutes
This guide will walk you through creating your first Wave animation. You’ll learn how to animate a view using spring physics and understand the core concepts.
Add a button or call this in viewDidAppear to trigger the animation:
func animateBox() { // Create a spring with some bounce let spring = Spring(dampingRatio: 0.6, response: 0.8) Wave.animate(withSpring: spring) { // Animate to center with scale and rotation self.box.animator.center = self.view.center self.box.animator.backgroundColor = .systemPink self.box.animator.scale = CGPoint(x: 1.5, y: 1.5) }}
Notice we use box.animator.center instead of box.center. This is required for Wave to track and animate the property.
That’s it! Run your app and you’ll see a smooth, spring-based animation with natural physics.
One of Wave’s most powerful features is the ability to inject gesture velocity into animations. This creates incredibly fluid, responsive interactions.
class ViewController: UIViewController { let draggableView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100)) override func viewDidLoad() { super.viewDidLoad() draggableView.backgroundColor = .systemOrange draggableView.layer.cornerRadius = 20 view.addSubview(draggableView) let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) draggableView.addGestureRecognizer(pan) } var initialCenter: CGPoint = .zero @objc func handlePan(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) let velocity = gesture.velocity(in: view) switch gesture.state { case .began: initialCenter = draggableView.center case .changed: // Use a tight spring for tracking the gesture let interactiveSpring = Spring(dampingRatio: 0.8, response: 0.2) Wave.animate(withSpring: interactiveSpring) { self.draggableView.animator.center = CGPoint( x: self.initialCenter.x + translation.x, y: self.initialCenter.y + translation.y ) } case .ended: // Use a looser spring for the final animation let animatedSpring = Spring(dampingRatio: 0.68, response: 0.8) // Inject the gesture velocity for natural momentum Wave.animate(withSpring: animatedSpring, gestureVelocity: velocity) { self.draggableView.animator.center = self.view.center } default: break } }}
Passing gestureVelocity to the animation preserves the momentum from the gesture, making the animation feel continuous and natural.
Wave supports completion callbacks that tell you when an animation finishes or gets retargeted:
Wave.animate(withSpring: Spring.defaultAnimated) { box.animator.alpha = 0.0} completion: { finished, retargeted in if finished { print("Animation completed successfully") // Remove the view, trigger another animation, etc. } if retargeted { print("Animation was interrupted and retargeted") // The target value changed mid-animation }}
finished: true if the animation completed normally
retargeted: true if the target value changed while animating
Retargeting is what makes Wave special. You can change an animation’s target value at any time, and Wave will smoothly redirect:
// Start animating to the rightWave.animate(withSpring: Spring.defaultAnimated) { box.animator.center.x = 300}// Before it finishes, change direction// The animation will smoothly arc to the new targetDispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { Wave.animate(withSpring: Spring.defaultAnimated) { self.box.animator.center.x = 100 }}
The second animation doesn’t abruptly reset the motion. Instead, Wave preserves the velocity and creates a smooth arc to the new destination.
Retargeting is especially powerful for gesture-driven UIs where users frequently change direction mid-interaction.