Every example below is a whole file. No helper types, no package, nothing to import beyond SwiftUI. Paste one into Xcode and it builds; open it in Swoop on your phone and it runs.

They are ordered by the SwiftUI idea each one teaches, not by how they look: implicit animation first, then repeating animation, then time-driven animation.

1. A pulse, with repeatForever

The smallest useful animation in SwiftUI: one piece of state, one modifier that animates on it, and a repeat. onAppear flips the state once and the repeat does the rest.

PulseBadge.swiftplays as written
import SwiftUI

struct PulseBadge: View {
    @State private var wide = false

    var body: some View {
        ZStack {
            Circle()
                .fill(.green)
                .frame(width: 20, height: 20)
                .scaleEffect(wide ? 2.7 : 1)
                .opacity(wide ? 0 : 0.55)
            Circle()
                .fill(.green)
                .frame(width: 20, height: 20)
        }
        .animation(.easeOut(duration: 1.6).repeatForever(autoreverses: false), value: wide)
        .onAppear { wide = true }
    }
}

2. A spinner, with rotationEffect

A trimmed circle is a spinner. trim cuts the stroke short, and rotating the whole shape forever does the spinning — there is no separate arc type to learn.

RingSpinner.swiftplays as written
import SwiftUI

struct RingSpinner: View {
    @State private var turning = false
    var size: Double = 46
    var lineWidth: Double = 5

    var body: some View {
        Circle()
            .trim(from: 0, to: 0.72)
            .stroke(.orange, lineWidth: lineWidth)
            .frame(width: size, height: size)
            .rotationEffect(.degrees(turning ? 360 : 0))
            .animation(.linear(duration: 1).repeatForever(autoreverses: false), value: turning)
            .onAppear { turning = true }
    }
}

3. A wave, with ForEach and a delay per item

The trick is the delay: index times a fraction of the period. Every dot runs the same animation, just later than the one before it.

DotWave.swiftplays as written
import SwiftUI

struct DotWave: View {
    @State private var up = false
    var count: Int = 5
    var period: Double = 1.05

    var body: some View {
        HStack(spacing: 7) {
            ForEach(0..<count, id: \.self) { i in
                Circle()
                    .fill(.cyan)
                    .frame(width: 9, height: 9)
                    .offset(y: up ? -9 : 9)
                    .animation(
                        .easeInOut(duration: period / 2)
                            .repeatForever()
                            .delay(Double(i) * period / 10),
                        value: up
                    )
            }
        }
        .onAppear { up = true }
    }
}

4. An equalizer, animating frame(height:)

Same shape as the wave, but the property being animated is the height of a capsule rather than its offset.

Equalizer.swiftplays as written
import SwiftUI

struct Equalizer: View {
    @State private var tall = false
    var bars: Int = 4

    var body: some View {
        HStack(alignment: .bottom, spacing: 5) {
            ForEach(0..<bars, id: \.self) { i in
                Capsule()
                    .fill(.purple)
                    .frame(width: 7, height: tall ? 42 : 14)
                    .animation(
                        .easeInOut(duration: 0.9)
                            .repeatForever()
                            .delay(Double(i) * 0.14),
                        value: tall
                    )
            }
        }
        .frame(height: 44)
        .onAppear { tall = true }
    }
}

5. A progress arc, animating trim

trim is animatable, so the arc can be driven straight from a Double. This is the same modifier as the spinner, moving instead of turning.

ProgressArc.swiftplays as written
import SwiftUI

struct ProgressArc: View {
    @State private var value: Double = 0.04

    var body: some View {
        Circle()
            .trim(from: 0, to: value)
            .stroke(.blue, lineWidth: 6)
            .frame(width: 46, height: 46)
            .rotationEffect(.degrees(-90))
            .animation(.easeInOut(duration: 1.3).repeatForever(), value: value)
            .onAppear { value = 0.92 }
    }
}

6. A bounce, with a spring

Springs are described by feel, not by curve: response is roughly how long the move takes and dampingFraction is how much it overshoots.

BouncingBall.swiftplays as written
import SwiftUI

struct BouncingBall: View {
    @State private var down = false

    var body: some View {
        ZStack(alignment: .top) {
            Circle()
                .fill(.pink)
                .frame(width: 18, height: 18)
                .offset(y: down ? 28 : 0)
        }
        .frame(width: 44, height: 48)
        .animation(
            .spring(response: 0.36, dampingFraction: 0.55).repeatForever(),
            value: down
        )
        .onAppear { down = true }
    }
}

7. A toggle that overshoots

Any spring with a damping fraction below about 0.7 overshoots. That is the whole difference between a switch that feels mechanical and one that feels alive.

BouncyToggle.swiftplays as written
import SwiftUI

struct BouncyToggle: View {
    @State private var on = false

    var body: some View {
        Button {
            on.toggle()
        } label: {
            Capsule()
                .fill(on ? Color.green : Color.gray.opacity(0.3))
                .frame(width: 50, height: 30)
                .overlay(alignment: on ? .trailing : .leading) {
                    Circle()
                        .fill(.white)
                        .frame(width: 24, height: 24)
                        .padding(3)
                }
        }
        .animation(.spring(response: 0.34, dampingFraction: 0.55), value: on)
    }
}

8. An orbit, driven by the clock

The others animate between two states. This one has no states: it reads the current time every frame and computes a position from it. That is what TimelineView(.animation) is for, and it is the only way to get motion that depends on continuous time rather than on a transition.

TwinOrbitLoader.swiftplays as written
import SwiftUI

struct TwinOrbitLoader: View {
    var size: Double = 62
    var period: Double = 0.85

    var body: some View {
        TimelineView(.animation) { context in
            let t = context.date.timeIntervalSinceReferenceDate
            let a = t * .pi * 2 / period

            ZStack {
                ball(angle: a, color: .mint)
                ball(angle: a + .pi, color: .cyan)
            }
        }
        .frame(width: size, height: size)
    }

    func ball(angle: Double, color: Color) -> some View {
        let depth = sin(angle)
        return Circle()
            .fill(color.gradient)
            .frame(width: size * 0.36, height: size * 0.36)
            .scaleEffect(0.78 + 0.22 * ((depth + 1) / 2))
            .offset(x: cos(angle) * size * 0.28, y: sin(angle) * size * 0.1)
            .zIndex(depth)
    }
}

Which one should you reach for

You wantUse
Something to change when a value changes.animation(_:value:)
Something to run forever.repeatForever() plus onAppear
Items to move one after another.delay(Double(i) * step) inside ForEach
Motion that depends on the clockTimelineView(.animation)
Overshoot.spring(dampingFraction:) below 0.7

Run them

Every file above is complete. Copy one into Swoop, press play, and change a number — the fastest way to understand a spring is to watch dampingFraction go from 0.9 to 0.4 while you are holding the phone.