import Portico /// Demonstrates Swift concurrency in Portico: a `Task` launched from a button /// handler, or from the view's own `.task` lifecycle modifier, performs work /// off the main actor in stages, and each stage's result is written straight /// into `@State` when the `await` resumes on the main actor. /// /// The whole module is `@MainActor`-isolated (`.defaultIsolation(MainActor.self)` /// in `Package.swift`), so `runAsyncWork()`'s body inherits main-actor /// isolation; only the explicit `Task.detached` blocks leave the main thread. struct AsyncDemoPage: View { private static let stageCount = 5 @State private var status = "Idle" @State private var progress = 0.0 @State private var total = 0 @State private var isRunning = false var body: some View { Clamp { StatusPage { VStack(spacing: 12) { Label { status } .title2() ProgressBar() .fraction { progress } .showText(true) .text { "\(Int(progress * 100))%" } .preferredWidth(240) Label { "Accumulated total: \(total)" } .dimmed() Button("Run Background Work") { Task { await runAsyncWork() } } .pill() .suggestedAction() .sensitive { !isRunning } } .halign(.center) .task { await runAsyncWork() } // scoped to mapped lifetime; cancelled on unmap } .title("Swift Concurrency") .description("Background stages update these widgets as they complete") .iconName("system-run-symbolic") } .hexpand(true) .vexpand(true) } /// Runs the staged background job. Re-entrant calls are ignored so a /// second click (or a carousel re-map) cannot interleave two runs. /// Cooperatively cancellable: checks `Task.isCancelled` before each /// stage, so unmapping the view (which cancels the `.task`-owned Task) /// stops the loop at the next stage boundary instead of running to /// completion. private func runAsyncWork() async { guard !isRunning else { return } isRunning = true defer { isRunning = false } status = "Starting..." progress = 0 total = 0 print("[AsyncDemo] started") for stage in 1...Self.stageCount { guard !Task.isCancelled else { print("[AsyncDemo] cancelled at stage \(stage)") return } // Off the main actor: this is where real network or CPU work goes. let chunk = await Self.work(stage: stage) // Resumed on the main actor - these three writes drive three // separate live widgets from inside the background job's loop. total += chunk progress = Double(stage) / Double(Self.stageCount) status = "Stage \(stage) of \(Self.stageCount)" print("[AsyncDemo] stage \(stage) -> total \(total)") } status = "Done: \(total)" print("[AsyncDemo] finished: \(total)") } /// One stage of genuinely off-main-actor work. private static func work(stage: Int) async -> Int { await Task.detached(priority: .userInitiated) { try? await Task.sleep(for: .milliseconds(300)) return stage * 10 }.value } }