Skip to content

Instantly share code, notes, and snippets.

@BrentMifsud
Last active July 26, 2025 15:44
Show Gist options
  • Save BrentMifsud/9168c29428ea69753e90afaaf6eda16b to your computer and use it in GitHub Desktop.
Save BrentMifsud/9168c29428ea69753e90afaaf6eda16b to your computer and use it in GitHub Desktop.
An SwiftUI button that executes an asynchronous action. The task is automatically cancelled when the lifetime of the button ends.
import SwiftUI
/// A Button that executes an asynchronous task. The task will be cancelled if the button's lifetime ends before the task does.
struct AsyncButton<Label: View>: View {
private let role: ButtonRole?
private let action: @Sendable @MainActor () async -> Void
private let label: Label
@Environment(\.isLoading) var isLoading
@State private var actionInProgress: Bool = false
@State private var task: Task<Void, Never>?
private var loadingInProgress: Bool {
actionInProgress || isLoading
}
/// A Button that executes an asynchronous task. The task will be cancelled if the button's lifetime ends before the task does.
/// - Parameters:
/// - role: An optional semantic role that describes the button. A value of nil means that the button doesn’t have an assigned role.
/// - action: The asynchronous action to perform when the user interacts with the button.
/// - label: A view that describes the purpose of the button’s action.
init(
role: ButtonRole? = nil,
action: @escaping @Sendable @MainActor () async -> Void,
label: () -> Label
) {
self.role = role
self.action = action
self.label = label()
}
var body: some View {
Button(role: role) {
task = Task { [action] in
await action()
}
} label: {
ZStack {
label
.opacity(loadingInProgress ? 0 : 1)
Image(systemName: "ellipsis")
.symbolEffect(.variableColor.iterative.dimInactiveLayers.nonReversing, options: .repeat(.continuous))
.font(.largeTitle)
.opacity(loadingInProgress ? 1 : 0)
}
}
.allowsHitTesting(!loadingInProgress)
.task(id: task) {
guard let action = task else {
return
}
actionInProgress = true
await action.value
task = nil
actionInProgress = false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment