Skip to content

Instantly share code, notes, and snippets.

@lukepistrol
Created October 17, 2022 21:00
Show Gist options
  • Save lukepistrol/67104dbe02f58d2b3c47495945828a57 to your computer and use it in GitHub Desktop.
Save lukepistrol/67104dbe02f58d2b3c47495945828a57 to your computer and use it in GitHub Desktop.
A SwiftUI Button that can perform asyncronous tasks.
import SwiftUI
/// A control that initiates an action asyncronously.
///
/// You create a button by providing an action and a label.
/// The action is either a method or closure property that
/// does something when a user clicks or taps the button.
/// The label is a view that describes the button’s action —
/// for example, by showing text, an icon, or both:
///
/// ```swift
/// AsyncButton(action: signIn) {
/// Text("Sign In")
/// }
/// ```
///
/// For the common case of text-only labels, you can use
/// the convenience initializer that takes a title string or
/// LocalizedStringKey as its first parameter, instead of
/// a trailing closure:
///
/// ```swift
/// AsyncButton("Sign In", action: signIn)
/// ```
///
/// How the user activates the button varies by platform:
/// - In iOS and watchOS, the user taps the button.
/// - In macOS, the user clicks the button.
/// - In tvOS, the user presses “select” on an external remote,
/// like the Siri Remote, while focusing on the button.
///
/// The appearance of the button depends on factors like
/// where you place it, whether you assign it a role, and
/// how you style it.
struct AsyncButton<Label: View>: View {
var action: () async -> Void
@ViewBuilder var label: () -> Label
var role: ButtonRole?
/// Creates an AsyncButton with a specified role,
/// that displays a custom label.
///
/// - 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 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 () async -> Void,
label: @escaping () -> Label
) {
self.action = action
self.label = label
self.role = role
}
@State private var isPerformingTask = false
var body: some View {
Button(role: role) {
isPerformingTask = true
Task {
await action()
isPerformingTask = false
}
} label: {
label()
}
.disabled(isPerformingTask)
}
}
extension AsyncButton where Label == Text {
/// Creates an AsyncButton with a specified role,
/// that displays a string as a label.
///
/// This init creates a Text automatically to display the given
/// string.
///
/// - Parameters:
/// - title: A String to display as a label.
/// - 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 action to perform when the user
/// interacts with the button.
init<S>(
_ title: S,
role: ButtonRole? = nil,
action: @escaping () async -> Void
) where S: StringProtocol {
self.init(role: role, action: action) {
Text(title)
}
}
/// Creates an AsyncButton with a specified role,
/// that displays a string as a label.
///
/// This init creates a Text automatically to display the string
/// for the given localized string key.
///
/// - Parameters:
/// - titleKey: A localized string key for displaying
/// a string as a label.
/// - 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 action to perform when the user
/// interacts with the button.
init(
_ titleKey: LocalizedStringKey,
role: ButtonRole? = nil,
action: @escaping () async -> Void
) {
self.init(role: role, action: action) {
Text(titleKey)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment