SwiftUI Progress Ring Component

A closed circular progress ring, determinate or indeterminate.

68%

Variants: Determinate, Indeterminate

Install the free SwiftUI progress ring

  1. Add SwiftKitUI.swift to your app once. It holds the tokens and motion curves.
  2. Copy SKProgressRing.swift below into your project. It needs nothing else.

Usage

Usage.swift
SKProgressRing(value: 0.4)
SKProgressRing()              // indeterminate

Full source · 96 lines · MIT

SKProgressRing.swift
//
//  SKProgressRing.swift
//  Swift Kit UI
//
//  Requires: SwiftKitUI.swift
//
//  A closed circular progress ring, determinate or indeterminate.
//
//  The indeterminate mode spins a fixed arc rather than animating `trim`
//  endpoints — rotating one layer is far cheaper than re-trimming a path 120
//  times a second, and it is the difference between a smooth ring and a
//  stuttering one on an older device.
//
//  Usage:
//      SKProgressRing(value: 0.4)
//      SKProgressRing()              // indeterminate
//

import SwiftUI

public struct SKProgressRing: View {
    private let value: Double?
    private let tint: Color
    private let lineWidth: CGFloat
    private let showsPercentage: Bool

    @State private var spin = false
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    public init(
        value: Double? = nil,
        tint: Color = SK.Palette.accent,
        lineWidth: CGFloat = 8,
        showsPercentage: Bool = false
    ) {
        self.value = value
        self.tint = tint
        self.lineWidth = lineWidth
        self.showsPercentage = showsPercentage
    }

    public var body: some View {
        ZStack {
            Circle()
                .stroke(SK.Palette.fill, lineWidth: lineWidth)

            if let value {
                Circle()
                    .trim(from: 0, to: min(max(value, 0), 1))
                    .stroke(tint, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
                    .rotationEffect(.degrees(-90))
                    .skAnimation(.settle, value: value)

                if showsPercentage {
                    Text("\(Int(min(max(value, 0), 1) * 100))%")
                        .font(SK.Type_.numeric)
                        .foregroundStyle(SK.Palette.label)
                        .contentTransition(.numericText())
                        .skAnimation(.snap, value: value)
                }
            } else {
                Circle()
                    .trim(from: 0, to: 0.22)
                    .stroke(tint, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
                    .rotationEffect(.degrees(spin ? 360 : 0))
                    .onAppear {
                        guard !reduceMotion else { return }
                        withAnimation(.linear(duration: 0.9).repeatForever(autoreverses: false)) {
                            spin = true
                        }
                    }
            }
        }
        .aspectRatio(1, contentMode: .fit)
        .accessibilityElement()
        .accessibilityLabel(value == nil ? "Loading" : "Progress")
        .accessibilityValue(value.map { Text("\(Int($0 * 100)) percent") } ?? Text(""))
    }
}

#Preview("SKProgressRing") {
    @Previewable @State var progress = 0.35

    VStack(spacing: SK.Space.xl) {
        HStack(spacing: SK.Space.xl) {
            SKProgressRing(value: progress, showsPercentage: true).frame(width: 96)
            SKProgressRing(value: 0.8, tint: SK.Palette.success).frame(width: 64)
            SKProgressRing().frame(width: 44)
        }
        Slider(value: $progress, in: 0...1).tint(SK.Palette.accent)
    }
    .padding(SK.Space.xl)
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(SK.Palette.surfaceSunken)
}

Questions

Is this SwiftUI progress ring free for commercial apps?
Yes. Swift Kit UI is MIT licensed, so you can use it in personal and commercial apps with no payment or sign-up. Keep the licence notice with the code.
What does it depend on?
SKProgressRing.swift depends only on SwiftKitUI.swift, a single foundation file with the design tokens and motion curves. There are no third-party packages.
Which iOS versions does it support?
iOS 17 and later, plus macOS 14.

Related components

Building a whole app? The Swift Kit adds auth, payments and onboarding, ready to ship.