Animation Curves and Animation Timing in SwiftUI | SwiftUI Bootcamp #26
Not all motion feels the same. A menu that snaps open with a spring feels interactive. A spinner that eases in and out feels calm. Understanding animation curves lets you choose the right feel for each interaction in your app rather than defaulting to whatever SwiftUI gives you.
What You'll Learn
- What an animation curve (easing function) is and why it changes how motion feels
- The difference between
linear,easeIn,easeOut,easeInOut, andspringin SwiftUI - How to tune a spring animation using
response,dampingFraction, andblendDuration
Mental Model
Imagine rolling a ball across a table. With linear animation, the ball moves at a perfectly constant speed the whole way — robotic, mechanical. With easeOut, the ball starts fast and gradually slows to a stop — like a real object decelerating due to friction. With easeIn, it starts slow and accelerates — like a car pulling away from a stop. easeInOut combines both: slow start, fast middle, slow finish — the most natural-feeling for UI transitions. spring goes a step further: the ball overshoots slightly and bounces back to its target, like a real spring. This overshoot is what gives spring animations their lively, tactile feel.
Detailed Explanation
An animation curve (formally called an "easing function") describes how much of the total distance is covered at each point in time. linear covers equal distance each frame. easeIn covers less distance early and more late. easeOut is the reverse. All these run for a fixed duration you specify.
Spring animations are different in nature: they don't run for a fixed duration. Instead, they simulate a physical spring with two parameters: response (how quickly the spring reaches its target — lower is faster) and dampingFraction (how much the spring oscillates — 1.0 means no bounce, values below 1.0 produce a bounce, values above 1.0 produce slow creep). blendDuration controls how smoothly the animation transitions when interrupted by a new state change mid-flight.
The practical choice:
linear— progress bars, mechanical counters, anything where constant speed is intentionaleaseOut— things entering the screen (they start off-screen fast and slow to a stop at the destination)easeIn— things leaving the screen (they start slow and accelerate off-screen)easeInOut— transitions that start and end visible (most standard UI changes)spring— interactive controls like buttons, toggles, and draggable elements where a physical feel is desired
Code Structure
AnimationTimingBootcamp.swift shows a live spring animation in the active code. The commented-out sections demonstrate linear, easeIn, easeInOut, and easeOut with the same element — uncomment each one, try toggling the button, and compare how the motion feels for each curve. The timing constant lets you slow all the duration-based curves down to 10 seconds so the differences are obvious.
Complete Code
AnimationTimingBootcamp.swift
import SwiftUI
struct AnimationTimingBootcamp: View {
@State var isAnimating: Bool = false
let timing: Double = 10.0 // intentionally slow — makes it easy to compare curve shapes visually
var body: some View {
VStack {
Button("Button") {
isAnimating.toggle()
}
RoundedRectangle(cornerRadius: 20)
.frame(width: isAnimating ? 350 : 50, height: 100) // width expands/contracts to show the curve
.animation(.spring(
response: 0.5, // time in seconds for the spring to reach its target (lower = snappier)
dampingFraction: 0.7, // 1.0 = no bounce, 0.0 = infinite bounce; 0.7 is a common sweet spot
blendDuration: 1.0)) // smooths out animation interruptions mid-flight
//.animation(.spring())
//.animation(Animation.linear(duration: timing))
// RoundedRectangle(cornerRadius: 20)
// .frame(width: isAnimating ? 350 : 50, height: 100)
// .animation(Animation.easeIn(duration: timing)) // slow start, fast finish
//
// RoundedRectangle(cornerRadius: 20)
// .frame(width: isAnimating ? 350 : 50, height: 100)
// .animation(Animation.easeInOut(duration: timing)) // slow start and finish, fast middle
//
// RoundedRectangle(cornerRadius: 20)
// .frame(width: isAnimating ? 350 : 50, height: 100)
// .animation(Animation.easeOut(duration: timing)) // fast start, slow finish
}
}
}
struct AnimationTimingBootcamp_Previews: PreviewProvider {
static var previews: some View {
AnimationTimingBootcamp()
}
}Code Walkthrough
let timing: Double = 10.0— A constant used by all the duration-based animation alternatives in the commented-out blocks. Setting this to 10 seconds makes each curve's shape visually obvious. Lower it to 0.5 for production use..animation(.spring(response: 0.5, dampingFraction: 0.7, blendDuration: 1.0))— The active animation.response: 0.5means the spring targets completion in about 0.5 seconds.dampingFraction: 0.7allows slight overshoot and bounce before settling.blendDuration: 1.0smooths out rapid re-taps.response: 0.5— Analogous to "stiffness" of the spring. Reduce toward 0.1 for a very snappy, high-energy feel. Increase toward 1.0 for a slower, more relaxed feel.dampingFraction: 0.7— Values below 1.0 add visible bounce. Try 0.3 for dramatic springiness. Try 1.0 for a critically damped spring (no overshoot at all, just smooth deceleration).Animation.easeOut(duration:)in the commented block — This is the most commonly used curve for views entering the screen, like a modal sliding up from the bottom.All four shapes in the commented block sharing
timing— The lesson is designed for you to uncomment all four simultaneously and compare them side by side. Each uses the same flag but demonstrates a distinctly different feel.
Common Mistakes
Mistake: Using linear for interactive elements like buttons and cards
Linear motion feels mechanical and unnatural for things users touch. Reserve linear for loading bars or counters. Use easeOut or spring for anything interactive.
Mistake: Setting dampingFraction to 0 or very close to 0
A damping fraction near 0 produces near-infinite oscillation — the view bounces forever. This is almost never what you want. Stay above 0.3 unless you specifically need a dramatic bounce for a game or onboarding effect.
Mistake: Using a long duration (like the timing: 10.0 constant here) in a production app
The 10-second duration is for learning. In real apps, most animations should complete in 0.2–0.5 seconds. Longer animations feel sluggish and frustrate users who tap repeatedly.
Key Takeaways
- Animation curves describe how a value changes over time, not just what it changes to — they determine whether motion feels mechanical, natural, or playful.
- Spring animations simulate physics and don't require a fixed duration;
responseanddampingFractiongive you precise control over feel. easeOutis the best default for views entering the screen;easeInfor views leaving;springfor interactive controls.
Last updated: June 27, 2026