2 min read
Step 3: Countdown
Implement countdown logic with start, pause, and reset.
Where We Are
You have a good-looking timer UI. Now let’s make it count down.
Add Timer State
Add state variables to track:
- Total duration in seconds (default: 25 * 60)
- Remaining seconds
- Whether the timer is running
Calculate the progress value (0.0 to 1.0) from remaining / total.
Use this to drive the ring's .trim(from:to:) value.
Start the Countdown
When the user taps Start:
- Create a Timer that fires every second
- Each tick, decrease remaining seconds by 1
- Update the progress ring with animation
- When remaining hits 0, stop the timer
Use Timer.publish(every: 1, on: .main, in: .common) and .onReceive.
The Start button should change to a Pause button (SF Symbol: "pause.fill")
when the timer is running.
Animate the Ring
Add a smooth animation to the ring progress so it doesn't jump.
Use .animation(.linear(duration: 1)) on the trim value
so the ring smoothly decreases each second.
Pause and Reset
Pause: Stop the timer but keep the remaining time. The button
should switch back to "play.fill".
Reset: Stop the timer and set remaining time back to the full duration.
The ring should animate back to full.
Format the Time
Display the remaining time as "MM:SS" format.
- 25 minutes = "25:00"
- 1 minute 5 seconds = "01:05"
- 0 seconds = "00:00"
Always show two digits for both minutes and seconds.
Test It
- Run the app
- Tap Start — the ring should shrink smoothly, time counts down
- Tap Pause — it stops
- Tap Start again — it resumes
- Tap Reset — ring goes back to full, time resets to 25:00
- Let it run to 00:00 — it should stop automatically
Checkpoint
By now you should have:
- Timer counts down from 25:00
- Ring animates smoothly as time decreases
- Start/Pause toggle works
- Reset returns to full duration
- Timer stops at 00:00
What You Learned
- Timer.publish for periodic updates
- SwiftUI animation with .animation()
- State management for play/pause/reset
- Time formatting with String(format:)