Spinner

A customizable loading spinner with smooth animations and multiple visual styles. Perfect for indicating loading states and async operations.

Import

import PrettyUI

Basic Usage

// Simple spinner
PSpinner()

// With label
PSpinner(label: "Loading...")

Sizes

Four sizes are available:

HStack(spacing: 32) {
    VStack {
        PSpinner().size(.sm)
        Text("SM").font(.caption)
    }
    VStack {
        PSpinner().size(.md)
        Text("MD").font(.caption)
    }
    VStack {
        PSpinner().size(.lg)
        Text("LG").font(.caption)
    }
    VStack {
        PSpinner().size(.xl)
        Text("XL").font(.caption)
    }
}
SizeDimensionStroke WidthBest For
.sm16pt2ptInline, buttons
.md24pt2.5ptDefault, cards
.lg32pt3ptOverlays, modals
.xl48pt4ptFull-screen loading

Styles

Five visual styles are available:

LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 24) {
    VStack {
        PSpinner().size(.lg).style(.circular)
        Text("Circular").font(.caption)
    }
    VStack {
        PSpinner().size(.lg).style(.dots)
        Text("Dots").font(.caption)
    }
    VStack {
        PSpinner().size(.lg).style(.track)
        Text("Track").font(.caption)
    }
    VStack {
        PSpinner().size(.lg).style(.minimal)
        Text("Minimal").font(.caption)
    }
    VStack {
        PSpinner().size(.lg).style(.orbit)
        Text("Orbit").font(.caption)
    }
}
StyleDescriptionBest For
.circularRotating arc (default)General purpose
.dotsPulsing dotsInline indicators
.trackGradient arc with trackProgress feel
.minimalSubtle arc with faint trackDark backgrounds
.orbitDots in circular orbitPremium feel

Custom Colors

Set a custom spinner color:

HStack(spacing: 32) {
    PSpinner().size(.lg).primary()  // Theme primary
    PSpinner().size(.lg).color(.green)
    PSpinner().size(.lg).color(.orange)
    PSpinner().size(.lg).color(.red)
}

With Labels

Add descriptive text:

// Label below (default)
PSpinner(label: "Loading...")
    .size(.lg)

// Label to the right
PSpinner(label: "Please wait")
    .size(.md)
    .labelPlacement(.trailing)

Label Placement

PSpinner(label: "Loading...")
    .labelPlacement(.bottom)    // Below spinner (default)

PSpinner(label: "Loading...")
    .labelPlacement(.trailing)  // Right of spinner

Spinner Overlay

Use the .spinnerOverlay() modifier to show a loading state over content:

PCard {
    VStack(spacing: 12) {
        Text("Card Content")
            .font(.headline)
        Text("This card has a loading state")
            .font(.caption)
            .foregroundColor(.secondary)
    }
    .padding(.vertical, 24)
}
.spinnerOverlay(isLoading, size: .lg)

Overlay Options

.spinnerOverlay(
    isLoading,           // Boolean binding
    size: .lg,           // Spinner size
    dimBackground: true  // Dim content when loading
)

Real-World Examples

Button Loading State

@State private var isLoading = false

PButton(isLoading ? "Loading..." : "Submit") {
    isLoading = true
    // ... async operation
}
.loading(isLoading)

Full-Screen Loading

ZStack {
    ContentView()
        .opacity(isLoading ? 0.3 : 1)
    
    if isLoading {
        PSpinner(label: "Loading wallet...")
            .size(.xl)
            .style(.track)
    }
}

Inline Loading

HStack {
    Text("Syncing")
    PSpinner()
        .size(.sm)
        .color(.secondary)
}

List Loading State

PList("Transactions") {
    if isLoading {
        HStack {
            Spacer()
            PSpinner(label: "Loading transactions...")
                .size(.md)
            Spacer()
        }
        .padding(.vertical, 32)
    } else {
        ForEach(transactions) { transaction in
            TransactionRow(transaction)
        }
    }
}

Refresh Indicator

ScrollView {
    LazyVStack {
        if isRefreshing {
            PSpinner()
                .size(.md)
                .style(.minimal)
                .padding()
        }
        // Content...
    }
}

Style Comparison

Circular vs Track

Use circular for general loading states:

PSpinner().style(.circular)

Use track when you want to convey progress:

PSpinner().style(.track)

Minimal vs Orbit

Use minimal on dark backgrounds or when you want subtlety:

PSpinner().style(.minimal)

Use orbit for a premium, distinctive feel:

PSpinner().style(.orbit)

API Reference

PSpinner Initializer

PSpinner(label: String? = nil)

Modifiers

ModifierTypeDescription
.size(_:)PSpinnerSizeSet the spinner size
.style(_:)PSpinnerStyleSet the visual style
.color(_:)ColorSet custom color
.primary()Use theme primary color
.label(_:)String?Set label text
.labelPlacement(_:)LabelPlacementLabel position

View Modifier

.spinnerOverlay(
    _ isLoading: Bool,
    size: PSpinnerSize = .md,
    dimBackground: Bool = true
)

Enums

PSpinnerSize

  • .sm — 16pt
  • .md — 24pt (default)
  • .lg — 32pt
  • .xl — 48pt

PSpinnerStyle

  • .circular — Rotating arc (default)
  • .dots — Pulsing dots
  • .track — Gradient arc with track
  • .minimal — Subtle arc
  • .orbit — Orbital dots

LabelPlacement

  • .bottom — Label below spinner (default)
  • .trailing — Label to the right

Accessibility

PSpinner automatically:

  • Respects reduced motion preferences (slower, simpler animations)
  • Uses theme colors for consistent appearance
  • Provides smooth, non-jarring animations