SwiftUI.cc
Snippets
DrawingOther ViewsIntermediate

SwiftUI Shapes, Path, Trim, and Stroke

Build reusable visual primitives with Shape, Path, stroke, fill, and trim instead of baking every decoration into image assets.

5 min readUpdated 2026-06
Progress ring
import SwiftUI

struct ProgressRing: View {
    let progress: Double

    var body: some View {
        ZStack {
            Circle()
                .stroke(.quaternary, lineWidth: 10)

            Circle()
                .trim(from: 0, to: progress)
                .stroke(.teal, style: StrokeStyle(lineWidth: 10, lineCap: .round))
                .rotationEffect(.degrees(-90))
        }
        .frame(width: 72, height: 72)
    }
}
Use this when

You need scalable progress rings, dividers, custom outlines, or reusable geometry.

A simple vector shape can replace a bitmap and adapt to dark mode automatically.

The same geometry will be filled, stroked, or animated in different places.

Avoid this when

The design requires complex illustration details better handled by an asset.

The shape is purely layout spacing; padding or frame is simpler.

The path has many hard-coded points that will break across sizes.

Implementation notes

trim is ideal for progress and reveal animations because it works on the normalized length of a shape.
strokeBorder keeps the stroke inside insettable shapes, which is often cleaner for cards and controls.
Paths should be built from the current rect or size whenever the shape must scale.

Checklist

Clamp progress between 0 and 1.

Use round line caps for circular progress indicators.

Preview with Dynamic Type if the shape sits near text.

Related reference