Use AsyncButton for closures that use async/await. The button manages task lifecycle: it prevents duplicate tasks if pressed while a task is already in progress, but remains hittable unless configured otherwise.
Loading State Management:
.disabledWhenLoading(): Disables the button while the task is running..allowsHitTestingWhenLoading(false): Disables hit testing while the task is running.
Monitoring State:
.onStateChange: React to .started(task) or .ended(completion)..onButtonStateChange: Monitor multiple buttons in a group..onButtonStateCancelled: React to specific button cancellations.
Visual Styles:
Use .asyncButtonStyle() to change how the button looks during loading (defaults to a ProgressView replacing the label).
.overlay (supports .overlay(style: .percent) for deterministic progress).pulse.leading.trailing.symbolEffect(.bounce).none (disables loading animation)
// Basic async usage
AsyncButton {
try await doSomethingThatTakeTime()
} label {
Text("Do something")
}
.disabledWhenLoading()
// Monitoring state
AsyncButton {
...
} onStateChange: { state in
switch state {
case let .started(task):
// Task started
case let .ended(completion):
// Task ended, failed or was cancelled
}
}