Duolingo-Style Progress Ring Animation on iOS (SwiftUI)
Two circles stacked, the top one trimmed to your progress, animated with a spring. That is the whole trick.
TL;DR
A Duolingo-style progress ring in SwiftUI is two stacked circles: a faint track and a colored progress arc drawn with Circle().trim(from: 0, to: progress), rotated so it starts at the top, with a rounded line cap. You animate it by changing the progress value inside withAnimation, and SwiftUI interpolates the trim, so you never redraw frames yourself. This is a learn-the-pattern guide, not a Duolingo asset. Start from a clean SwiftUI layout, like a free VP0 design at $0, and drop the ring into your streak or lesson screen.
The satisfying ring that fills as you finish a lesson looks complicated and is not. It is two stacked circles, the top one trimmed to your progress and animated. Once you see the pattern, you can build a progress ring for a streak, a goal, a timer, or a download in a few minutes. This is a learn-the-pattern guide to the SwiftUI technique, not a Duolingo asset to copy. Here is the code shape and the small details that sell it. Drop it into a clean screen from a free VP0 design (the free iOS and React Native design library AI builders read from) at $0.
The pattern: two circles, one trimmed
A progress ring is a ZStack of two circles. The back one is a faint full Circle stroked as the track. The front one is a Circle with .trim(from:to:), where progress is a value from 0 to 1, stroked in your accent color. That trim is what turns a full circle into an arc. Three details make it look right:
| Detail | Modifier | Effect |
|---|---|---|
| Start at the top | .rotationEffect(.degrees(-90)) | Arc begins at 12 o’clock, not 3 |
| Soft ends | .stroke(style: StrokeStyle(lineCap: .round)) | The rounded Duolingo cap |
| Thick line | lineWidth on both strokes | A chunky, readable ring |
By default a trimmed circle starts at the 3 o’clock position, which is why the -90 degree rotation matters. The rounded line cap is the small touch that separates a polished ring from a sharp, technical one.
Animating it without redrawing
Here is the part people overcomplicate: you do not animate frames. You change the progress value inside withAnimation, and SwiftUI interpolates the trim’s end point for you. A .spring() gives the lively, slightly bouncy fill that feels rewarding; an .easeOut gives a calmer one. Apple’s SwiftUI animation overview covers the timing curves. Because the shape is declarative, the same approach scales to a progress ring animation in SwiftUI for any metric, and it is the native cousin of creating animated React components easily, where you also describe states rather than frames. For the celebratory moment when the ring completes, pair it with something like the Duolingo streak flame animation.
Keep it honest: pattern, not clone
The circular progress control is a common pattern you are free to build. What is protected is Duolingo’s specific branding: the owl mascot, the exact color system, the sound design, and their assets. So build the technique, then style it as your own. If you are assembling a gamified screen, the Duolingo-style gamification UI assets discussion covers where that line sits. Keep your ring, your colors, your copy.
Driving it from real data
In a real app progress comes from somewhere: lessons completed over lessons total, minutes done over a daily goal, bytes downloaded over total. Compute that ratio, clamp it to 0 through 1, and feed it to the trim. Update it inside withAnimation whenever the underlying number changes, and the ring animates to the new value on its own. That is the entire data path, which keeps the view simple and testable.
The same restrained-spring craft applied to chat appears in the iMessage bubble physics build, where sent bubbles spring more eagerly than received ones.
Key takeaways
- A progress ring is two stacked circles; the front one uses
.trim(from: 0, to: progress). - Rotate the arc
-90degrees to start at the top, and use a round line cap for the soft look. - Animate by changing
progressinsidewithAnimation; SwiftUI tweens the trim, no frames. - Build the pattern, not Duolingo’s branding; style the ring as your own.
- Drop it into a clean VP0 streak or lesson screen at $0.
Frequently asked questions
How do I make a circular progress ring in SwiftUI?
Stack two circles. Draw a faint full Circle as the track, then a second Circle with .trim(from: 0, to: progress) and .stroke for the arc, where progress is 0 to 1. Rotate the arc by -90 degrees so it starts at the top, and use a round line cap for the soft Duolingo look. Animate by changing progress inside withAnimation and SwiftUI tweens the trim for you.
How do I animate the ring filling up?
Change the progress value inside a withAnimation block, or attach .animation to the trim. SwiftUI interpolates the trim’s end value over the duration, so the arc grows smoothly without you computing any frames. A spring animation gives the bouncy, lively feel; an easeOut gives a calmer fill.
Why is my SwiftUI ring starting from the wrong side?
By default a trimmed circle starts at the 3 o’clock position and goes clockwise. To start at the top like a progress ring, rotate the shape by -90 degrees with .rotationEffect(.degrees(-90)). If it animates the wrong direction, flip the trim range or the rotation accordingly.
Is it legal to copy Duolingo’s progress ring?
You can build a circular progress ring; the control itself is a common UI pattern. What you should not copy is Duolingo’s exact branding, mascot, colors, and assets, which are protected. This guide teaches the SwiftUI technique so you can build your own ring with your own styling, not clone their app.
What is the best way to add a progress ring to an iOS app?
Build it from a trimmed Circle and animate the trim, then place it in a clean screen. A free VP0 design, the free iOS and React Native design library for AI builders, gives you the streak or lesson layout to drop the ring into and generate in Cursor or Claude Code at $0.
What the VP0 community is asking
How do I make a circular progress ring in SwiftUI?
Stack two circles. Draw a faint full Circle as the track, then a second Circle with .trim(from: 0, to: progress) and .stroke for the arc, where progress is 0 to 1. Rotate the arc by -90 degrees so it starts at the top, and use a round line cap for the soft Duolingo look. Animate by changing progress inside withAnimation and SwiftUI tweens the trim for you.
How do I animate the ring filling up?
Change the progress value inside a withAnimation block, or attach .animation to the trim. SwiftUI interpolates the trim's end value over the duration, so the arc grows smoothly without you computing any frames. A spring animation gives the bouncy, lively feel; an easeOut gives a calmer fill.
Why is my SwiftUI ring starting from the wrong side?
By default a trimmed circle starts at the 3 o'clock position and goes clockwise. To start at the top like a progress ring, rotate the shape by -90 degrees with .rotationEffect(.degrees(-90)). If it animates the wrong direction, flip the trim range or the rotation accordingly.
Is it legal to copy Duolingo's progress ring?
You can build a circular progress ring; the control itself is a common UI pattern. What you should not copy is Duolingo's exact branding, mascot, colors, and assets, which are protected. This guide teaches the SwiftUI technique so you can build your own ring with your own styling, not clone their app.
What is the best way to add a progress ring to an iOS app?
Build it from a trimmed Circle and animate the trim, then place it in a clean screen. A free VP0 design, the free iOS and React Native design library for AI builders, gives you the streak or lesson layout to drop the ring into and generate in Cursor or Claude Code at $0.
Part of the UI Animations, Gamification & Microinteractions hub. Browse all VP0 topics →
Keep reading
Wheel of Fortune Spinner UI Template for iOS
A free SwiftUI spinner pattern: a smooth spin that lands fairly on a segment, with disclosed odds, Reduce Motion support, and no pay-to-spin. A reward, not gambling.
Leaderboard Podium Animation for iOS (Free SwiftUI Pattern)
Build a leaderboard podium that ranks your top three with a satisfying rise-into-place animation in SwiftUI, accessible and free, starting from a VP0 design.
Confetti Cannon Animation in SwiftUI (Free Pattern)
A confetti cannon celebrates a real win with a burst of particles. Build it in SwiftUI, fire it only on genuine success, and respect Reduce Motion.
Duolingo-Style Streak Flame Animation in SwiftUI
Build a Duolingo-style streak flame animation in SwiftUI: a flame that grows with the streak and celebrates a daily win, from a free VP0 design.
Meditation Breathing Circle Animation in SwiftUI
Build a calming breathing-guide animation in SwiftUI: a circle that expands and contracts to pace breathing, with haptics, from a free VP0 design.
Rive Interactive Button in React Native
Build an interactive button with Rive in React Native: idle, press, loading, and success states in one animation, from a free VP0 design.