All work
Solace app icon
Concept app · SwiftUI · iOS 18+ · 2026

Solace

A calmer phone.

A premium meditation, sleep and wellbeing app in the spirit of Calm × Oura × Apple Fitness+: living aurora gradients, frosted glass, editorial serif type and a breathing orb at its centre.

Role
Design & iOS engineering
Platform
iPhone · iOS 18+
Stack
SwiftUI · Observation · MeshGradient · Swift Charts
30screens captured
23guided sessions
6coaches
14days of health data
5tabs
Screen tour

Eight chapters, one app.

Scroll through Solace the way a user meets it, from first launch to the last tab. Each chapter is a real simulator capture.

Chapter 01 · Splash

A point of light blooms.

No spinner, no logo slam. A single point of light swells into the orb that anchors the whole app, while the aurora wakes up behind it and the wordmark settles in.

  • TimelineView
  • MeshGradient
Chapter 02 · Today

Today, written like a morning letter.

A time-aware greeting, “The Daily Solace” hero, then For You, Tonight, collections, a quote card and habits, each on frosted glass over the living gradient.

  • glassCard()
  • SessionArtwork
  • time-aware copy
Chapter 03 · Mood & rings

Check in. See yourself.

A one-tap mood check-in responds with a gentle suggestion, beside a mindful-minutes ring and readiness, sleep and HRV tiles whose numbers roll rather than jump.

  • ProgressRing
  • contentTransition(.numericText())
  • sensoryFeedback
Chapter 04 · Player

A player that breathes with you.

The full-screen player pulses the orb in time with the session, with a scrubber, chapters and AirPlay. Finishing lands on a “Beautifully done.” ring and your streak.

  • custom scrubber
  • symbolEffect
  • fullScreenCover
Chapter 05 · Explore

A library worth wandering.

A featured carousel, kind filters, collections, Trending, guides with a coach sheet, soundscapes with live equalizers, and a dedicated Sleep category.

  • presentationDetents
  • scrollTargetBehavior
  • live equalizer
Chapter 06 · Breathe

Inhale. Hold. Exhale.

Four techniques: Box 4·4·4·4, 4-7-8, Coherence 5·5 and Energize 2·2. The orb expands and contracts on a phase-timed engine, counts down each phase and fires a haptic as it changes.

  • phase-timed engine
  • async/await
  • sensoryFeedback
Chapter 07 · Insights

Health data, drawn gently.

A readiness ring, a custom sleep hypnogram, sleep duration bars, an HRV line and area chart, mindful minutes against goal and 14 days of mood dots, all animated across 7 and 14-day ranges.

  • Swift Charts
  • RectangleMark
  • catmullRom
Chapter 08 · You

Progress you can feel.

A profile with a streak heatmap, achievements that unlock with symbol effects, favourites and settings for reminders, goals and Apple Health.

  • Grid heatmap
  • symbolEffect(.bounce)
  • Observation
Under the hood

Built the native way.

Modern SwiftUI end to end, with no third-party UI libraries. All data is static and mocked so the craft stays front and centre.

Signature

An aurora that is alive

The background is an iOS 18 MeshGradient whose centre control point drifts on a TimelineView. It costs one view, never repeats exactly, and sits behind every screen.

Aurora.swift
struct Aurora: View {
    var body: some View {
        TimelineView(.animation) { context in
            let t = context.date.timeIntervalSinceReferenceDate
            MeshGradient(width: 3, height: 3, points: [
                [0, 0], [0.5, 0], [1, 0],
                [0, 0.5], drift(t), [1, 0.5],
                [0, 1], [0.5, 1], [1, 1]
            ], colors: Palette.aurora)
            .ignoresSafeArea()
        }
    }
}
Motion

Numbers that roll

Rings and stats animate their digits instead of snapping.

MindfulRing.swift
Text(minutes, format: .number)
  .contentTransition(
    .numericText())
  .animation(.snappy,
             value: minutes)
Engine

A phase-timed breath

Each technique is data. The engine walks its phases with structured concurrency, driving the orb scale, the countdown and a haptic on every change.

BreathEngine.swift
static let box = Technique("Box", [
    (.inhale, 4), (.hold, 4), (.exhale, 4), (.hold, 4)
])

func run(_ technique: Technique) async {
    for (phase, seconds) in technique.pattern {
        self.phase = phase      // orb: 0.62 ↔ 1.0
        tick.toggle()           // haptic trigger
        for left in stride(from: seconds, to: 0, by: -1) {
            countdown = left
            try? await Task.sleep(for: .seconds(1))
        }
    }
}
Data viz

A custom hypnogram

Sleep stages are RectangleMarks on a time axis; HRV is a catmull-rom LineMark over a gradient AreaMark, with a RuleMark for the goal.

SleepChart.swift
Chart(night.stages) { stage in
    RectangleMark(
        xStart: .value("Start", stage.start),
        xEnd: .value("End", stage.end),
        y: .value("Stage", stage.kind.label),
        height: .fixed(10))
    .foregroundStyle(stage.kind.color)
    .clipShape(.capsule)
}

LineMark(x: .value("Day", day.date, unit: .day),
         y: .value("HRV", day.ms))
    .interpolationMethod(.catmullRom)
Design system

Glass, rings and artwork as components

A small design system keeps 30 screens consistent: a glassCard modifier, a reusable ProgressRing and SessionArtwork that renders every session cover from gradients and symbols.

GlassCard.swift
extension View {
    func glassCard(radius: CGFloat = 28) -> some View {
        padding(20)
            .background(.ultraThinMaterial, in: .rect(cornerRadius: radius))
            .overlay {
                RoundedRectangle(cornerRadius: radius)
                    .strokeBorder(.white.opacity(0.12))
            }
    }
}
Automated showcase capture

Every screen on this page was captured by a script.

This is QA-automation thinking applied to marketing assets. A launch argument puts the app in demo mode, a driver walks the real UI, and OSLog markers tell a recorder exactly when to grab each frame. When the design changes, every asset is regenerated with one command.

  1. 01-demoMode launch argument
  2. 02DemoDriver walks every screen
  3. 03OSLog emits SHOT markers
  4. 04simctl records and snaps on cue

30 screens + a 1:42 walkthrough, zero hand-edited frames

DemoMode.swift
import OSLog

let demo = Logger(subsystem: "com.aya.solace", category: "demo")

if CommandLine.arguments.contains("-demoMode") {
    Task { await DemoDriver(store: store).run() }
}

// DemoDriver drives the real UI, then marks the frame
await show(.breathe(.box, phase: .inhale))
demo.notice("SHOT 19-breathe-inhale")
capture.sh
xcrun simctl io booted recordVideo demo.mov &
xcrun simctl launch --console booted com.aya.solace -demoMode |
while read line; do              # every SHOT marker = one frame
  [[ $line == *SHOT* ]] || continue
  xcrun simctl io booted screenshot "${line##* }.png"
done
Full walkthrough

1:42 of Solace, unedited.

Every tab, sheet and transition, recorded straight from the iOS simulator by the automated demo driver.

0:00 / 1:42

Muted · 1:42 · 590×1280 simulator capture