Docs
Controls

Timer Dial

A countdown ring you set by dragging a glass knob around it, with a tick per step, a warning zone and start, pause and reset bubbles.

$npx swiftpieces add TimerDial
See it in a Pro app On a real screen in one of the Pro apps: open it with Pro, take it apart, remix it, and copy the SwiftUI.
Free · MIT + Commons ClauseiOS 17.0+Liquid Glasstimercountdowndialringprogresshapticsglass
Type
TimerDial
Files
TimerDial.swift
Depends on
Nothing (Apple frameworks only)
Version
2.2.0
In a full screen · SwiftPieces ProFocusA focus session with a dial you drag to set minutes and a ring that drains as you work.

Settable countdown ring, or a determinate progress ring with the same look. The ring and its ticks are content; the knob riding the ring is a liquid glass bubble, and style.showsControls adds start, pause and reset as glass bubbles under the dial: reset buds out of the start bubble while there is something to reset.

Notes

  • While idle or paused, dragging around the ring sets seconds like a kitchen timer: the angle maps to 0...maxSeconds in step increments. The knob rides under your finger and the ring follows it, with no lag; the value, the digits and the lit ticks move in whole steps. Let go and the knob clicks into its detent on a short spring from rest, so it never swings past the value shown, and seats with a small pop. Touching anywhere else on the ring moves the knob straight there. Touching the dial while paused starts the countdown over from the dialed length.
  • The knob is a clear liquid glass bubble riding the ring, so the ring's color shows through it. It is larger while the dial can be set, and lifts while held: a little larger again, with a deeper shadow. It drops back with an elastic give and a small pop when you let go, while the ring and the value land firm. It stays on the ring throughout, at 12 o'clock once a countdown is done or a progress ring is empty.
  • The value stops at one step and at a full turn. Past either end the knob gives with rubber-band resistance, never more than about its own width, while the value stays at the end; let go and it springs back at the speed it was moving, on the snap spring rather than an elastic one, since the ring is the time: it passes the end back into the range by under a point when let go at rest, and by a few points at most when flicked back. Drag on more than half a turn past an end and the knob comes round to your finger, taking the value there. Crossing 12 o'clock never wraps the value from one end to the other, and touching the knob at the top of a full dial grabs it there rather than emptying the dial.
  • Haptics: .selection per step, throttled so a fast spin over 1s steps clicks instead of buzzing, and a rigid impact on every multiple of 5 and at either end. VoiceOver adjusts it with swipe up/down; those changes, a new seconds from your code and a new progress value all land on the same short spring, with no overshoot to misread, unless you animate the change yourself. Under your finger the ring and knob never animate, even inside an animated transaction of yours.
  • One face throughout: the ring shows the time left against maxSeconds, so it winds to the set time and drains back to 12 o'clock from there. A seconds longer than maxSeconds, set from your code, is measured against itself instead, so that countdown still drains from a full ring; dialing clamps to maxSeconds. The look comes from Style.standard: a solid signal ring over a raised track, a clear glass knob, a 60-tick scale inside that brightens under the value, and semibold display numerals reading m:ss with the leading 0: dimmed. Style.sky swaps the ring; colors still overrides it (several colors draw an angular gradient). knob no longer does anything: the knob is glass.
  • Timing is anchored to a start date and rendered by TimelineView, so the ring never drifts; a single .task sleeps until the finish. Toggling isRunning off banks the elapsed time; on resumes. After finishing, true restarts from seconds. Changing seconds resets.
  • Digits roll toward the new value, up when time is added and down as it counts, without rolling past it, and change at once under your finger. The caption (Ready, Remaining, Paused, Done) morphs letter by letter from one phase to the next.
  • Style(showsControls: true) adds start, pause and reset under the countdown dial as liquid glass bubbles. Start is glass tinted like the ring, its glyph swapping between play and pause as it toggles isRunning. Reset buds out of it to rest beside it, apart, while the dial is running, paused or done, the pair keeping centred, and melts back into it once the dial is ready again; it stops the countdown and sets the dial back to seconds. Off by default, so a dial with your own controls looks as it did.
  • At or below warningAt the signal ring fades in over the base ring and the digits turn signal. The dial beats once a second, up to 1.2%: each beat peaks as the digits change, with a soft impact, and the beat fades in and out instead of popping, including when you pause. Finishing springs a full sage ring in under the knob and plays .success.
  • TimerDial(progress:) is the determinate mode: the same ring and knob with a percent readout (dimmed %) and no clock or dial. Turn showsTicks off and lower digitSize for small rings.
  • Digits scale with Dynamic Type relative to .largeTitle and never outgrow the ring. Reduce Motion keeps the knob tracking your finger but stops it firm at the ends, drops the lift scale and the landing pop (the shadow still deepens), lands without overshoot, fades the reset bubble in and out in place, crossfades the digits and the caption, ticks the ring once per second and skips the finish scale. In the warning zone the beat becomes a brief brightening of the ring each second, and the soft impact still plays. Reduce Transparency draws the glass solid.
  • Need a whole focus session with task binding and breaks? SwiftPieces Pro has the Focus screen (focus-timer).

Usage

TimerDialExample().preferredColorScheme(.light)

Parameters

ParameterDescription
secondsBound countdown length. Dragging around the ring while idle or paused rewrites it.
isRunningSet true to run, false to pause. Flips to false when the countdown finishes; set it back to true to restart.
stepSeconds per dial detent. Every multiple of 5 plays a heavier tick.
maxSecondsValue at a full turn of the dial.
warningAtRemaining seconds at or below which the ring turns to style.warning and breathes.
lineWidthRing thickness in points.
colorsOverrides style.fill: one color for a flat ring, or several for an angular gradient. nil uses the style.
captionText under the digits. Defaults to the phase (Ready, Remaining, Paused, Done).
styleRing, track, knob, tick, warning and finish colors plus the numeral size. .standard is the house palette with a tangerine ring.
onFinishCalled once when the ring reaches zero.
progressDeterminate mode: a 0–1 value with a percent readout and no clock.

Install

Pick one. Run it from the folder that contains your .xcodeproj and the files land inside your app.

Terminal

$npx swiftpieces add TimerDial

Or ask your coding agent · Claude Code, Cursor or Xcode, once the MCP server is connected

>Add the SwiftPieces "Timer Dial" piece to my app

Or copy the source above into your app.

New to SwiftUI?If you know React, CSS or Tailwind, SwiftUI is easier to pick up than it looks.Try real app patterns first

Building a whole app? See SwiftPieces Pro →

All buttons and controls →

Build it yourself: SwiftUI motion guide · SwiftUI buttons guide · SwiftUI haptics guide

On this page

SwiftPiecesPro

Build the whole app with one library.

Production-ready screens, whole flows, complete app templates, a Build Kit your agent follows and Pro remixing.

54 screens · 46 patterns · 48 flows · 12 templates · 31 Build Kit skills

Explore Pro