Home Guides Loading animation

A SwiftUI loading animation

A SwiftUI loading animation is a timeline, not a spinner asset. Two dots take opposite sides of a slow ellipse, and the clock keeps them moving.

A sketch of the motion. The file below plays on the phone, and the same source pastes into Xcode.

The file

import SwiftUI

struct OrbitLoader: View {
    var body: some View {
        TimelineView(.animation) { context in
            let t = context.date.timeIntervalSinceReferenceDate
            let a = t * .pi * 2 / 0.9
            ZStack {
                Circle()
                    .fill(.mint)
                    .frame(width: 16, height: 16)
                    .offset(x: cos(a) * 22, y: sin(a) * 8)
                Circle()
                    .fill(.cyan)
                    .frame(width: 16, height: 16)
                    .offset(x: cos(a + .pi) * 22, y: sin(a + .pi) * 8)
            }
        }
        .frame(width: 64, height: 64)
    }
}

timeIntervalSinceReferenceDate is a number that grows. Dividing by 0.9 sets the period: one turn every nine tenths of a second. cos and sin turn that angle into an x and a y. The second dot adds .pi, so it stays opposite the first.

The y distance is smaller than the x distance, 8 against 22, so the path reads as a flat orbit instead of a circle drawn on the screen. offset does the placing. A stack would only center the pair.

Why a timeline

A spring runs until a value settles. A loader must not settle. TimelineView(.animation) asks for a new date on each frame, and the body is a pure function of that date, so the motion has no state to get stuck in.

The home page shows a fuller version of this, with depth and two colors passed in. This one is the smallest file that still reads as loading. Rotation is the same clock applied to an angle. Offset is the same clock applied to one dot.

Keep going

Get Swoop

iPhone. iOS 17 and later. The file plays on the phone.