Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ on:

jobs:
build:
runs-on: macos-15
runs-on: macos-26
steps:
- uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Show Xcode version
run: xcodebuild -version
- name: Validate package
run: swift package dump-package
- name: Build package for iOS Simulator
Expand All @@ -21,7 +21,7 @@ jobs:
- name: Test package
run: >-
xcodebuild -scheme Aither
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=latest'
-destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=latest'
test
- name: Build preview application
run: >-
Expand Down
89 changes: 66 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Aither

Aither is a dependency-free UIKit package for expressive, continuously looping
particle orbs. Every animation is rendered with Core Graphics, works without
SwiftUI, and can respond smoothly to live application data.
Aither is a dependency-free package for expressive, continuously looping
particle orbs. Its primary API is native SwiftUI, backed by an efficient
UIKit/Core Graphics renderer that can also be used directly.

## Requirements

Expand All @@ -27,15 +27,72 @@ The package has no third-party dependencies.
## Quick start

```swift
import SwiftUI
import Aither

AitherOrb()
.aitherAnimation(.aither)
.aitherColors(.cyan, .purple)
.aitherSpeed(1.2)
.aitherIntensity(audioLevel)
.frame(width: 96, height: 96)
```

`aitherIntensity(_:)` accepts a normalized live value such as an audio level or
network activity. When SwiftUI state changes, Aither updates the existing
renderer instead of recreating it.

## Data-driven motion

Drive motion directly from SwiftUI state:

```swift
AitherOrb()
.aitherAnimation(.synthesis)
.aitherColors(.cyan, .indigo)
.aitherIntensity(audioLevel)
.aitherProgress(uploadProgress)
.aitherEmphasis(notificationStrength)
.aitherStatus(isComplete ? .success : .active)
.frame(width: 96, height: 96)
```

All normalized inputs are clamped to `0...1` by the renderer. Changes are
interpolated by default, so noisy data does not produce visual jitter.

## SwiftUI configuration

| Modifier | Purpose |
| --- | --- |
| `aitherAnimation(_:)` | Selects a continuously looping motion system. |
| `aitherColors(_:_:)` | Sets a depth-aware one- or two-color palette. |
| `aitherSpeed(_:)` | Sets the base playback-rate multiplier. |
| `aitherIntensity(_:)` | Drives motion with normalized live activity data. |
| `aitherProgress(_:)` | Supplies optional normalized progress. |
| `aitherEmphasis(_:)` | Supplies a normalized transient emphasis value. |
| `aitherStatus(_:)` | Sets idle, active, success, warning, or failure state. |
| `aitherParticleIntensity(_:)` | Controls particle contrast and opacity. |
| `aitherDensity(_:)` | Balances particle detail and rendering cost. |
| `aitherGlow(_:)` | Controls foreground particle halos. |
| `aitherDirection(_:)` | Selects forward or reverse playback. |
| `aitherPhaseOffset(_:)` | Desynchronizes multiple orb instances. |
| `aitherPhase(_:)` | Supplies an external phase; `nil` restores looping. |
| `aitherResponse(_:)` | Sets the live-data interpolation duration. |
| `aitherEasing(_:)` | Selects instant, smooth, spring, or cubic Bézier response. |
| `aitherQuality(_:)` | Selects automatic, low, or high rendering quality. |
| `aitherActivityMapping(_:)` | Maps activity to selected visual properties. |
| `aitherPaused(_:)` | Pauses or resumes playback from SwiftUI state. |

## UIKit

`AitherView` remains available for UIKit applications and advanced imperative
integration:

```swift
let orb = AitherView()
.animation(.energeia)
.palette(primary: .systemCyan, secondary: .systemPurple)
.speed(1.2)
.intensity(0.9)
.density(0.85)
.glow(0.6)

orb.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(orb)
Expand All @@ -48,20 +105,7 @@ NSLayoutConstraint.activate([
])
```

`AitherView` is a regular `UIView`; size and layout remain under Auto Layout or
frame-based control.

## Data-driven motion

Update live values independently:

```swift
orb.setActivity(networkActivity)
orb.setProgress(uploadProgress)
orb.setEmphasis(notificationStrength)
```

Or apply one coherent input snapshot:
Apply one coherent UIKit input snapshot:

```swift
orb.update(
Expand All @@ -73,8 +117,7 @@ orb.update(
)
```

All normalized inputs are clamped to `0...1`. Changes are interpolated by
default so noisy data does not produce visual jitter:
Configure how UIKit input is interpolated:

```swift
orb
Expand Down Expand Up @@ -116,7 +159,7 @@ orb.stopAnimating()

Available statuses are `idle`, `active`, `success`, `warning`, and `failure`.

## Configuration
## UIKit configuration

| API | Purpose |
| --- | --- |
Expand Down
Loading
Loading