Back to OSS
Swift Package UI / SwiftUI

swift-statable

AsyncValue パターンで Observable な状態管理を実現する Swift マクロ

Swift
swift-macrostate-managementasync

Statable

English | 日本語

A declarative state management macro for SwiftUI. Combines the AsyncValue pattern with OperationTracker to manage asynchronous state in a type-safe manner.

Swift 6.0+ iOS 17+ macOS 14+ License

Features

  • Declarative Macro: Reduce state management boilerplate with the @Statable macro
  • Exclusive State Representation: Type-safe expression of .idle, .loading, .loaded, .failed with AsyncState<T> enum
  • Operation Tracking: Track multiple concurrent operations individually with OperationTracker
  • @Observable Integration: Fully integrated with SwiftUI's @Observable
  • Sendable Conformance: Full Strict Concurrency support

Quick Start

import SwiftUI
import Statable

// Simple Store definition
@Statable(MetabolicProfile.self)
@MainActor @Observable
final class ProfileStore {
    public init() {}

    var currentAge: Int { value?.age() ?? 0 }
}

// Store with operation tracking
enum WorkoutOperation: String, CaseIterable, Sendable {
    case fetch, recordStrength, recordCardio
}

@Statable([WorkoutActivity].self, operations: WorkoutOperation.self)
@MainActor @Observable
final class WorkoutStore {
    public init() {}

    var isRecording: Bool {
        operations.isActive(.recordStrength) || operations.isActive(.recordCardio)
    }
}

Installation

Swift Package Manager

Add the following to your Package.swift:

dependencies: [
    .package(url: "https://github.com/no-problem-dev/swift-statable.git", from: "1.0.2")
]

Add to your target:

.target(
    name: "YourTarget",
    dependencies: [
        .product(name: "Statable", package: "swift-statable")
    ]
)

Usage

Basic Store

@Statable(UserProfile.self)
@MainActor @Observable
final class UserStore {
    public init() {}
}

// Usage in View
struct ProfileView: View {
    @Environment(UserStore.self) private var store

    var body: some View {
        switch store.state {
        case .idle:
            Text("No data")
        case .loading(let previous):
            VStack {
                ProgressView()
                if let prev = previous {
                    Text("Previous: \(prev.name)")
                }
            }
        case .loaded(let profile):
            Text("Hello, \(profile.name)")
        case .failed(let error):
            Text("Error: \(error.localizedMessage)")
        }
    }
}

Loading Data

// Basic load
await store.load {
    try await api.fetchProfile()
}

// Load only if no value exists
await store.loadIfNeeded {
    try await api.fetchProfile()
}

// Force reload
await store.reload {
    try await api.fetchProfile()
}

Operation Tracking

enum DataOperation: String, CaseIterable, Sendable {
    case fetch, save, delete
}

@Statable([Item].self, operations: DataOperation.self)
@MainActor @Observable
final class ItemStore {
    public init() {}
}

// Tracking operations
struct ItemListView: View {
    @Environment(ItemStore.self) private var store

    var body: some View {
        List {
            if store.operations.isActive(.fetch) {
                ProgressView("Loading...")
            }

            ForEach(store.value ?? []) { item in
                ItemRow(item: item)
            }
        }
        .toolbar {
            Button("Save") {
                Task {
                    await store.operations.run(.save) {
                        try await api.saveItems(store.value ?? [])
                    }
                }
            }
            .disabled(store.operations.isActive(.save))
        }
    }
}

API Reference

@Statable Macro

Generated Properties

Property Type Description
value T? Current value
state AsyncState<T> State (for switch)
isLoading Bool Whether loading
isIdle Bool Whether idle
isFailed Bool Whether failed
hasValue Bool Whether value exists
error StateError? Error
operations OperationTracker<Op> Operation tracker (only with operations argument)

Generated Methods

Method Description
set(_:) Set value
setError(_:) Set error
startLoading() Start loading
reset() Reset to initial state
load(_:) Execute async operation
loadIfNeeded(_:) Load only if no value
reload(_:) Force reload

AsyncState

public enum AsyncState<Value: Sendable>: Sendable {
    case idle                       // Initial state
    case loading(previous: Value?)  // Loading (retains previous value)
    case loaded(Value)              // Load succeeded
    case failed(StateError)         // Load failed
}

OperationTracker

// Start/complete operations
operations.start(.fetch)
operations.complete(.fetch)
operations.fail(.fetch, with: error)

// Check state
operations.isActive(.fetch)
operations.hasActiveOperations
operations.error(for: .fetch)

// Convenience method
await operations.run(.fetch) {
    try await api.fetchData()
}

StateError

public enum StateError: Error, Sendable, Equatable, Hashable {
    case network(NetworkError)
    case validation(ValidationError)
    case notFound(resource: String)
    case unauthorized
    case server(code: Int, message: String)
    case unknown(String)
}

// Convenience properties
error.localizedMessage  // User-facing message
error.isRetryable       // Whether retry is appropriate

// Convert from standard Error
let stateError = StateError(from: someError)

Design Principles

1 Store = 1 AsyncValue

Each Store manages a single type of async value. This ensures:

  • State consistency
  • Easy testing
  • Clear responsibilities

SSOT (Single Source of Truth)

The AsyncState enum represents exclusive states, preventing contradictory states (e.g., isLoading = true AND error != nil) at the type level.

Previous Value During Loading

loading(previous: Value?) allows displaying the previous value during reload, improving UX.

Documentation

Detailed API documentation is available on GitHub Pages.

Dependencies

Package Purpose
swift-syntax Macro implementation

License

MIT License - See LICENSE for details.

同じカテゴリの OSS — UI / SwiftUI

swift-design-system

Swift Package

SwiftUI 向けの型安全で拡張可能なデザインシステム

Swift
· UI / SwiftUI
swiftuidesign-systemios

swift-ui-routing

Swift Package

SwiftUI 向けの型安全で宣言的なルーティングライブラリ

Swift
· UI / SwiftUI
swiftuiroutingnavigation

swift-markdown-view

Swift Package

DesignSystem 統合とシンタックスハイライトを備えた SwiftUI ネイティブな Markdown レンダリング

Swift
· UI / SwiftUI
swiftuimarkdownsyntax-highlighting

swift-latex-view

Swift Package

SwiftUI ネイティブな LaTeX 数式レンダリング。LLM 出力にも堅牢

Swift
· UI / SwiftUI
swiftuilatexmath

メモリ & ディスクの二層キャッシュで高速表示する SwiftUI リモート画像

Swift
· UI / SwiftUI
swiftuiimage-cacheasync

Google Slides API の JSON を SwiftUI で描画。A2A アーティファクトのストリーミングに対応

Swift
· UI / SwiftUI
swiftuigoogle-slidesa2a

swift-document-scanner

Swift Package

矩形検出・OCR・カメラ撮影を備えた iOS 向けドキュメントスキャン基盤

Swift
· UI / SwiftUI
iosscannerocr

swift-voice-input

Swift Package

ストリーミング認識とフローティングプレビュー UI を備えたプロトコル指向の音声入力

Swift
· UI / SwiftUI
voicespeechswiftui

© 2026 Kyoichi Taniguchi. All rights reserved.