|
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) |
|
} |
|
} |
|
} |