SwiftUI Animations for Beginners: Learn with Simple Examples (2025 Edition)
Animations in SwiftUI feel like magic 🪄 — until your view just snaps instead of fades. In this beginner-friendly guide, we’ll animate…

🌀 Introduction: Why SwiftUI Animations Aren’t Just Eye Candy
Animations in SwiftUI can feel like magic 🪄. Tap a button, and a view gracefully fades in. Toggle a switch, and a box zooms into place. No UIView.animate, no delegates, no fuss. Just... a state change.
But there’s something deeper going on. When a view animates in SwiftUI, it’s not because a function was called to “move it.” Instead, SwiftUI re-renders your view with updated state, and — if it detects the change is part of an animation — it interpolates between the old and new values over time. That’s right: it animates the difference between two states, not the thing itself.
This approach is beautiful… and sometimes frustrating. You wrap a state change in withAnimation and nothing happens. You apply .animation() and still no fade. Or worse—your view just blinks instead of sliding.
This guide is here to clear up all that confusion.
🔍 What You’ll Learn
This beginner-friendly guide covers:
- How
withAnimationworks, what it actually does, and when to use it - What the
.animation()modifier does — and whyvalue:is not optional - How transitions like
.slideand.opacitytrigger on view insert/removal - What’s new in SwiftUI animations as of iOS 17+
- Common beginner mistakes — and how to fix them without pulling your hair out
We’ll explore these concepts through simple, clean examples you can drop into a SwiftUI preview and see live in action.
And if you’re ready to go beyond the basics? Check out the advanced animations guide here, where we dive into matchedGeometryEffect, TimelineView, and more.
**Advanced Animations in SwiftUI: matchedGeometryEffect, TimelineView, PhaseAnimator & Beyond (2025…** _In this deep dive, we’re exploring SwiftUI’s most advanced animation tools — from layout-matching transitions and…_medium.comhttps://medium.com/swift-pal/advanced-animations-in-swiftui-matchedgeometryeffect-timelineview-phaseanimator-beyond-2025-da8876b7b0b9
Let’s kick things off with SwiftUI’s foundational animation tool: withAnimation.
📌 1. withAnimation — SwiftUI’s Animation Trigger
Let’s start with the most common animation tool in SwiftUI:
withAnimation {
isVisible.toggle()
}At first glance, this might look like a fancy UIView.animate, but it’s very different. There are no callbacks, no frames, and you're not animating a view directly. You’re telling SwiftUI:
“Hey, I’m about to change some state. Animate whatever changes result from that.”
SwiftUI takes that signal, observes the updated @State, and checks if any view properties that can animate (like **opacity**, **scale**, **position**) have changed. If yes, it interpolates them over time.
🎬 Under the Hood: What’s Actually Happening?
When you use withAnimation, SwiftUI starts an animation transaction. Think of it like a timeline: any view change that occurs during that window will be animated.
// Example 1: Implicit animation via state change
@State private var isVisible = false
var body: some View {
VStack {
Button("Toggle") {
withAnimation(.easeInOut(duration: 0.5)) {
isVisible.toggle()
}
}
if isVisible {
Rectangle()
.fill(.blue)
.frame(width: 100, height: 100)
.transition(.slide)
}
}
}In this code:
withAnimationcreates the animation context.isVisible.toggle()triggers a re-render of the view tree.- SwiftUI sees that a
Rectangleis being inserted with a.slidetransition. - The transition interpolates the rectangle’s position from off-screen to on-screen using the
easeInOutcurve.
📌 If you remove withAnimation, the rectangle still appears—but it’ll pop into place, not slide.
🆕 New in iOS 17+: withAnimation(..., completion:)
This one’s a game-changer: SwiftUI finally lets you know when an animation is done.
withAnimation(.easeInOut(duration: 1.0)) {
isVisible.toggle()
} completion: {
print("Animation finished!")
}This is especially helpful when:
- You want to chain animations or stages (e.g. slide → fade)
- You need to update UI state after the transition ends
- You want to show a message, alert, or confetti 🎉
🧪 Example: Animate with Completion Message
struct AnimatedToggleView: View {
@State private var isVisible = false
@State private var message = ""
var body: some View {
VStack(spacing: 20) {
Button("Show Box") {
if #available(iOS 17.0, *) {
withAnimation(.easeInOut(duration: 0.6)) {
isVisible.toggle()
} completion: {
message = isVisible ? "🎉 Animation complete!" : ""
}
} else {
withAnimation {
isVisible.toggle()
}
}
}
if isVisible {
Rectangle()
.fill(.mint)
.frame(width: 120, height: 120)
.transition(.scale)
}
if !message.isEmpty {
Text(message)
.font(.headline)
}
}
.padding()
}
}📝 On iOS 17 and above, you’ll see a clean scale transition and a message that appears only after the animation is complete. Below iOS 17, it will still animate — just without the callback.
📌 2. .animation() Modifier — What It Really Does (and Why It Confuses Everyone)
The .animation() modifier was one of the original ways to animate views in SwiftUI. It looks so simple:
Circle()
.frame(width: isExpanded ? 200 : 100)
.animation(.spring(), value: isExpanded)And hey, it works! The circle resizes smoothly when isExpanded changes. So what’s the problem?
🧠 What .animation() Actually Does
It attaches an implicit animation to the view, telling SwiftUI:
“Whenever the _value:_ you gave me changes, animate all animatable properties in this view using this animation.”That value: part? Crucial. If you leave it out or set it to the wrong thing, your animation might:
- Never trigger 🤷
- Fire at the wrong time 🔁
- Animate even when it shouldn’t 😱
⚠️ The Silent Trap
A lot of early tutorials (and devs) used .animation(.easeInOut) without the value::
.opacity(isVisible ? 1 : 0)
.animation(.easeInOut) // ❌ This might not work as expected in recent SwiftUI versionsThis worked pre-iOS 15 because SwiftUI used to infer animation triggers more liberally. But since iOS 15+, you must specify the value you’re watching:
.opacity(isVisible ? 1 : 0)
.animation(.easeInOut, value: isVisible) // ✅ Works predictablyIf isVisible changes, SwiftUI interpolates the .opacity from the old value to the new one using .easeInOut.
🧪 Example: Resizing Circle with .animation()
struct AnimatedCircleView: View {
@State private var isExpanded = false
var body: some View {
VStack(spacing: 20) {
Button("Toggle Size") {
isExpanded.toggle()
}
Circle()
.fill(.blue)
.frame(width: isExpanded ? 200 : 100, height: isExpanded ? 200 : 100)
.animation(.spring(response: 0.4, dampingFraction: 0.6), value: isExpanded)
}
.padding()
}
}Every time you tap the button, SwiftUI compares the new isExpanded value with the previous one. Since it changed, it animates the frame difference using a spring.
🧪 What Is a Spring Animation?
Imagine stretching a rubber band and letting it go. It doesn’t just stop instantly — it overshoots, rebounds, and settles. A spring animation replicates this effect digitally by simulating mass and tension.
In SwiftUI, the .spring() modifier allows you to control that behavior with fine-tuned parameters:
🧩 Parameter Breakdown
1\. response: 0.4
- Think of this as the duration of one bounce.
- Lower value = faster animation
- Higher value = slower and more elastic
🧠 Analogy: Response is like how “eager” the spring is to react.
2\. dampingFraction: 0.6
- This controls how much the spring resists bouncing.
- Range is from
0to1: 0= infinite bouncing (chaotic)1= no bounce (settles instantly)
A value of 0.6 means the animation will have some bounce, but not too much — like a well-tuned UI that feels responsive yet natural.
📌 Combined Effect
So this line:
.spring(response: 0.4, dampingFraction: 0.6)…creates an animation that:
- Responds fairly quickly (in 0.4 seconds per cycle)
- Has a moderate amount of bounce before settling
⛔ Should You Still Use .animation()?
Well… yes and no.
✅ Use .animation() when:
- You want a passive animation on view state
- You’re animating simple, local changes
- You like clean view hierarchies and minimal code
❌ Avoid it when:
- You need to animate only in specific conditions
- You want to attach side effects (e.g. show alert after animation)
- You’re using
transition(), which doesn’t respect.animation()well —withAnimationis better for that
In newer SwiftUI designs (especially iOS 16+), developers often prefer **withAnimation** because:
- It’s more explicit
- It’s easier to debug
- It works better with conditionals, transitions, and gestures
🔍 TL;DR: When to Use .animation() vs withAnimation
- ✅ Use
.animation()when you want:
- Simple, passive animations tied to a specific value
- Cleaner code for things like color or scale changes
- Animations that always run when a value changes
2\. ❌ Avoid .animation() when:
- You’re animating view appearance/disappearance (use
withAnimation+.transition()instead) - You need conditional logic around the animation
- You want to do something after the animation ends (e.g. trigger another action)
📌 3. Making Views Appear/Disappear with transition(_:)
SwiftUI doesn’t animate views the way UIKit did. In UIKit, you’d animate opacity or frame changes directly. In SwiftUI, views don’t even exist until your state says they do — so if a view appears or disappears, you’re dealing with creation or removal, not transformation.
That’s where transition(_:) comes in. It lets SwiftUI animate the insertion or removal of a view when it appears or disappears as a result of a state change.
🪄 Basic Usage: Slide a View In
if isVisible {
Rectangle()
.fill(.blue)
.frame(width: 100, height: 100)
.transition(.slide)
}Wrap the state change in withAnimation:
withAnimation {
isVisible.toggle()
}📌 This animates the view entering and leaving with a slide — as long as SwiftUI knows it’s being inserted or removed.
🧠 But Wait: Why Doesn’t It Always Work?
Here’s the catch — and it trips up a lot of beginners:
_transition(_:)__only works on conditional views_ — like those created with_if_, not just views being shown/hidden with opacity.
Wrong:
Rectangle()
.opacity(isVisible ? 1 : 0) // ❌ Not a transition — no insertion/removal
.transition(.slide) // Doesn’t do anythingRight:
if isVisible {
Rectangle()
.transition(.slide) // ✅ Now SwiftUI sees it as a new view
}🧪 Example: Slide + Fade
You can even combine transitions:
.transition(.asymmetric(insertion: .move(edge: .bottom), removal: .opacity))This lets you say:
- “When the view appears, slide it up.”
- “When it disappears, just fade it out.”
💡 SwiftUI gives you .opacity, .scale, .slide, and .move(edge:) out of the box — or you can define custom transitions for advanced use cases.
🧩 Putting It All Together
struct TransitionExampleView: View {
@State private var showCard = false
var body: some View {
VStack(spacing: 20) {
Button("Toggle Card") {
withAnimation(.easeInOut(duration: 0.4)) {
showCard.toggle()
}
}
if showCard {
RoundedRectangle(cornerRadius: 16)
.fill(.purple)
.frame(width: 200, height: 120)
.transition(.move(edge: .bottom).combined(with: .opacity))
}
}
.padding()
}
}Tapping the button causes the card to slide in from the bottom and fade in, and do the reverse when toggled off.
✅ Quick Tips for transition(_:):
- 🔒 Must be on a conditional view (e.g.
if showCard { ... }) - 🔗 Needs to be paired with
withAnimation { }to animate - ⚠️ Doesn’t affect views that just change opacity or scale — only insertion/removal
- ⚙️ Use
.asymmetricto customize entry/exit animations
📌 4. Try This Yourself: A SwiftUI Animation Playground 🎮
Alright, enough theory — time to get your hands on the code. SwiftUI animations are best understood when you feel them. So here’s a simple playground to start experimenting with what you’ve learned.
🧪 Example: A Bouncing Ball
struct BouncingBallView: View {
@State private var moveDown = false
var body: some View {
VStack {
Spacer()
Circle()
.fill(.orange)
.frame(width: 80, height: 80)
.offset(y: moveDown ? 300 : 0)
.animation(.interpolatingSpring(stiffness: 120, damping: 8), value: moveDown)
Spacer()
Button("Bounce!") {
moveDown.toggle()
}
}
.padding()
}
}🧠 What Does That Animation Line Actually Do?
Let’s zoom in on this line:
.animation(.interpolatingSpring(stiffness: 120, damping: 8), value: moveDown)This line tells SwiftUI:
“Whenever the_moveDown_value changes, animate this view’s_offset_using a spring-based animation.”
Let’s break it down:
🔹 .animation(...)
This attaches an animation to the view, telling SwiftUI to animate it whenever the **value:** changes — in this case, moveDown.
Without this, the circle would just jump to the new position.
🔹 .interpolatingSpring(...)
This is where the real magic happens.
It’s a spring animation — like attaching the circle to a rubber band and letting it snap into place. But unlike the older .spring() modifier, this one gives you fine control through:
**stiffness**: how tight the spring is- High stiffness = fast, snappy
- Low stiffness = loose, slow
- In our case,
stiffness: 120gives a nice responsive snap **damping**: how much bounce gets absorbed- Low damping = bouncy like a trampoline
- High damping = smooth, settles with no bounce
- Here,
damping: 8adds a touch of bounce without going full cartoon
Together, this combo gives the ball a clean, lively movement — not too bouncy, not too rigid.
🎯 Your Challenge (Optional, But Fun)
Try modifying the animation! See how it feels when you:
- Change
stiffnessto40→ super stretchy 🧸 - Change
dampingto3→ more bounce than a toddler on a sugar high - Swap to
.easeInOutand compare the difference - Add
.scaleEffect()to make the ball squash/stretch when it lands
🛠️ Dev Tip:
Use Canvas Preview or Live Previews in Xcode. SwiftUI is designed to give instant feedback — and this is where it shines. No need to build and run. Just tweak the code and watch it bounce.
🏁 Wrap Up: Your First Steps into SwiftUI Animations
Animations in SwiftUI aren’t just for making things look fancy — they’re a core part of how your UI communicates intent. And as you’ve seen, they’re incredibly powerful once you understand how they work:
✅ withAnimation wraps your state changes in an animation transaction ✅ .animation(..., value:) ties changes to specific values for implicit animations ✅ transition(_:) controls how views enter and exit ✅ And spring animations give your interface that natural, fluid feel users love
You’re no longer just flipping switches and watching boxes appear — you’re coordinating motion in a declarative, elegant way.
🎯 What You Can Do Next:
Before jumping to advanced animation APIs like matchedGeometryEffect, TimelineView, or gesture-based animations:
- Try building a custom transition
- Animate between multiple views (like cards in a stack)
- Chain animations using
withAnimation(..., completion:) - Build a mini onboarding flow using transitions and spring effects
All of that builds on the same foundation you’ve now laid brick by brick.
📌 Next:
Our next article will dive into **advanced SwiftUI animations**, covering topics like:
- Matched Geometry Effect (for magical layout transitions ✨)
- TimelineView (for time-driven animations and effects)
- PhaseAnimator (to animate across multiple stages)
- How animations work across the SwiftUI rendering pipeline
It’s going to be a wild ride — but now you’ve got the skills to handle it.
**Advanced Animations in SwiftUI: matchedGeometryEffect, TimelineView, PhaseAnimator & Beyond (2025…** _In this deep dive, we’re exploring SwiftUI’s most advanced animation tools — from layout-matching transitions and…_medium.comhttps://medium.com/swift-pal/advanced-animations-in-swiftui-matchedgeometryeffect-timelineview-phaseanimator-beyond-2025-da8876b7b0b9
🎉 Enjoyed this article? Your support means the world to me!
💬 Drop a comment below! I love hearing about your experiences and answering questions
🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal
💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal
Happy coding! 🚀
New articles, straight to your inbox.
No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.
Keep reading
Why I'm Rebuilding My Blog From Scratch (and Leaving Medium)
After years of publishing on someone else's platform, I'm moving my writing to a home I actually own. Here's the reasoning, and what I'm building instead.
ReadData Is the Model: The Most Ignored Part of AI
A beginner-friendly guide to why data quality beats model hype.
Read