Skip to content

Instantly share code, notes, and snippets.

@lanserxt
Created August 15, 2026 15:58
Show Gist options
  • Select an option

  • Save lanserxt/16dc46607b455bf9e7f4d9a3d72352d2 to your computer and use it in GitHub Desktop.

Select an option

Save lanserxt/16dc46607b455bf9e7f4d9a3d72352d2 to your computer and use it in GitHub Desktop.
iOS 27: StateReporting Framework
import SwiftUI
import Observation
import StateReporting
// MARK: - StateReporting Metadata
@available(iOS 27.0, *)
@ReportableMetadata
struct CleanupStableMetadata: Equatable {
/// A low-cardinality value that changes how cleanup behaves.
let mode: String
/// Another low-cardinality configuration value.
let scanStrategy: String
}
@available(iOS 27.0, *)
@ReportableMetadata
struct CleanupVolatileMetadata: Equatable {
/// Changes while the same state is active, so it belongs in volatile metadata.
let progress: Double
let filesFound: Int
let reclaimableMegabytes: Double
}
// MARK: - Model
@available(iOS 27.0, *)
@MainActor
@Observable
final class StorageCleanupModel {
enum Phase: String, CaseIterable {
case idle = "Idle"
case scanning = "Scanning"
case cleaning = "Cleaning"
case completed = "Completed"
case failed = "Failed"
}
private static let reportingDomain =
"com.example.storage-cleaner.cleanup"
/// StateReporter is long-lived and unique for this domain.
private let reporter = StateReporter.reporter(
for: reportingDomain,
stableMetadata: CleanupStableMetadata.self,
volatileMetadata: CleanupVolatileMetadata.self
)
// UI state
private(set) var phase: Phase = .idle
private(set) var progress = 0.0
private(set) var filesFound = 0
private(set) var reclaimableMegabytes = 0.0
private(set) var isRunning = false
// Stable metadata for this demo run.
let cleanupMode = "Temporary Files"
let scanStrategy = "Safe"
var progressText: String {
progress.formatted(
.percent.precision(.fractionLength(0))
)
}
var reclaimableText: String {
let measurement = Measurement(
value: reclaimableMegabytes,
unit: UnitInformationStorage.megabytes
)
return measurement.formatted(
.measurement(
width: .abbreviated,
usage: .general
)
)
}
// MARK: Cleanup Simulation
func startCleanup() async {
guard !isRunning else {
return
}
isRunning = true
filesFound = 0
reclaimableMegabytes = 0
progress = 0
transition(to: .scanning)
// Deliberately sampled at human-interaction timescales.
// StateReporting should not be updated in a tight loop or every frame.
for step in 1...5 {
guard !Task.isCancelled else {
reset()
return
}
try? await Task.sleep(for: .milliseconds(700))
filesFound += 280
reclaimableMegabytes += 180
progress = Double(step) / 5.0
updateVolatileMetadata()
}
progress = 0
transition(to: .cleaning)
for step in 1...5 {
guard !Task.isCancelled else {
reset()
return
}
try? await Task.sleep(for: .milliseconds(700))
progress = Double(step) / 5.0
updateVolatileMetadata()
}
progress = 1
transition(to: .completed)
isRunning = false
}
func simulateFailure() {
guard !isRunning else {
return
}
progress = 0
transition(to: .failed)
}
func reset() {
phase = .idle
progress = 0
filesFound = 0
reclaimableMegabytes = 0
isRunning = false
// nil means that this domain currently has no active state.
reporter.reportTransition(to: nil)
}
// MARK: StateReporting
private var stableMetadata: CleanupStableMetadata {
CleanupStableMetadata(
mode: cleanupMode,
scanStrategy: scanStrategy
)
}
private var volatileMetadata: CleanupVolatileMetadata {
CleanupVolatileMetadata(
progress: progress,
filesFound: filesFound,
reclaimableMegabytes: reclaimableMegabytes
)
}
private func transition(to newPhase: Phase) {
phase = newPhase
reporter.reportTransition(
to: newPhase.rawValue,
stableMetadata: stableMetadata,
volatileMetadata: volatileMetadata
)
}
private func updateVolatileMetadata() {
reporter.reportVolatileMetadataUpdate(
volatileMetadata
)
}
}
// MARK: - View
@available(iOS 27.0, *)
struct StorageCleanupView: View {
@State private var model = StorageCleanupModel()
var body: some View {
NavigationStack {
List {
stateSection
.listRowSpacing(8)
metadataSection
controlsSection
}
.navigationTitle("Storage Cleanup")
}
}
private var stateSection: some View {
Section("Current State") {
LabeledContent(
"Phase",
value: model.phase.rawValue
)
if model.phase == .failed {
Label(
"Cleanup failed. No files were removed.",
systemImage: "xmark.octagon.fill"
)
.foregroundStyle(.red)
}
VStack(alignment: .leading, spacing: 8) {
ProgressView(value: model.progress)
HStack {
Text("Progress")
Spacer()
Text(model.progressText)
.foregroundStyle(.secondary)
}
}
LabeledContent(
"Files Found",
value: model.filesFound.formatted()
)
LabeledContent(
"Reclaimable",
value: model.reclaimableText
)
}
}
private var metadataSection: some View {
Section("Stable Metadata") {
LabeledContent(
"Mode",
value: model.cleanupMode
)
LabeledContent(
"Strategy",
value: model.scanStrategy
)
}
}
private var controlsSection: some View {
Section {
Button {
Task {
await model.startCleanup()
}
} label: {
Label(
"Run Cleanup",
systemImage: "sparkles"
)
}
.disabled(model.isRunning)
Button(role: .destructive) {
model.simulateFailure()
} label: {
Label(
"Simulate Failure",
systemImage: "exclamationmark.triangle"
)
}
.disabled(model.isRunning)
Button {
model.reset()
} label: {
Label(
"Reset",
systemImage: "arrow.counterclockwise"
)
}
.disabled(model.isRunning)
}
}
}
#Preview {
if #available(iOS 27.0, *) {
StorageCleanupView()
} else {
// Fallback on earlier versions
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment