-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add TaskTriggerUI Package * implement TaskTriggerButton * fix file headers * fix available annotation for all platforms
- Loading branch information
1 parent
7d0b1d5
commit c489972
Showing
12 changed files
with
550 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
Sources/TaskTrigger/TaskTriggerButton/TaskTriggerButton+Behavior.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// TaskTriggerButton+Behavior.swift | ||
// TaskTrigger | ||
// | ||
// Created by Lukas Pistrol on 26.11.24. | ||
// | ||
|
||
import Foundation | ||
|
||
extension TaskTriggerButton { | ||
/// Describes how the button behaves when tapped and a task is running. | ||
/// | ||
/// All can show a separate placeholder while the task is running. | ||
public enum Behavior { | ||
/// The button is disabled while the task is running. A placeholder can be shown while the task is running. | ||
case blocking(showPlaceholder: Bool) | ||
|
||
/// The button can be tapped again to cancel the task. A placeholder can be shown while the task is running. | ||
case cancellable(showPlaceholder: Bool) | ||
|
||
/// The button can be tapped again to cancel and restart the task. A placeholder can be shown while the task | ||
/// is running. | ||
case restart(showPlaceholder: Bool) | ||
|
||
var showPlaceholder: Bool { | ||
switch self { | ||
case .blocking(let showPlaceholder): | ||
return showPlaceholder | ||
case .cancellable(let showPlaceholder): | ||
return showPlaceholder | ||
case .restart(let showPlaceholder): | ||
return showPlaceholder | ||
} | ||
} | ||
|
||
var isBlocking: Bool { | ||
if case .blocking = self { | ||
return true | ||
} | ||
return false | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
Sources/TaskTrigger/TaskTriggerButton/TaskTriggerButton+Init.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// | ||
// TaskTriggerButton+Init.swift | ||
// TaskTrigger | ||
// | ||
// Created by Lukas Pistrol on 26.11.24. | ||
// | ||
|
||
import SwiftUI | ||
|
||
extension TaskTriggerButton { | ||
/// Creates a new ``TaskTriggerButton`` with a title. | ||
/// - Parameters: | ||
/// - text: The button's title. | ||
/// - role: The button's role. Defaults to `nil`. | ||
/// - behavior: The button's behavior. Defaults to `.blocking(showPlaceholder: true)`. | ||
/// - action: The async action to perform when the button is tapped. | ||
/// - placeholder: The placeholder to show while the task is running. Defaults to a `ProgressView`. | ||
/// If `showPlaceholder` on the `role` is set to `false`, this is ignored. | ||
public init( | ||
_ text: LocalizedStringKey, | ||
role: ButtonRole? = nil, | ||
behavior: Behavior = .blocking(showPlaceholder: true), | ||
action: @escaping @Sendable @MainActor () async -> Void, | ||
@ViewBuilder placeholder: @escaping () -> P = { ProgressView() } | ||
) where L == Text { | ||
self.action = action | ||
self.label = { Text(text) } | ||
self.placeholder = placeholder | ||
self.role = role | ||
self.behavior = behavior | ||
} | ||
|
||
/// Creates a new ``TaskTriggerButton`` with a title and an image. | ||
/// - Parameters: | ||
/// - text: The button's title. | ||
/// - image: The button's image. | ||
/// - role: The button's role. Defaults to `nil`. | ||
/// - behavior: The button's behavior. Defaults to `.blocking(showPlaceholder: true)`. | ||
/// - action: The async action to perform when the button is tapped. | ||
/// - placeholder: The placeholder to show while the task is running. Defaults to a `ProgressView`. | ||
/// If `showPlaceholder` on the `role` is set to `false`, this is ignored. | ||
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) | ||
public init( | ||
_ text: LocalizedStringKey, | ||
image: ImageResource, | ||
role: ButtonRole? = nil, | ||
behavior: Behavior = .blocking(showPlaceholder: true), | ||
action: @escaping @Sendable @MainActor () async -> Void, | ||
@ViewBuilder placeholder: @escaping () -> P = { ProgressView() } | ||
) where L == Label<Text, Image> { | ||
self.action = action | ||
self.label = { Label(text, image: image) } | ||
self.placeholder = placeholder | ||
self.role = role | ||
self.behavior = behavior | ||
} | ||
|
||
/// Creates a new ``TaskTriggerButton`` with a title and a system image. | ||
/// - Parameters: | ||
/// - text: The button's title. | ||
/// - systemImage: The button's system image. | ||
/// - role: The button's role. Defaults to `nil`. | ||
/// - behavior: The button's behavior. Defaults to `.blocking(showPlaceholder: true)`. | ||
/// - action: The async action to perform when the button is tapped. | ||
/// - placeholder: The placeholder to show while the task is running. Defaults to a `ProgressView`. | ||
/// If `showPlaceholder` on the `role` is set to `false`, this is ignored. | ||
public init( | ||
_ text: LocalizedStringKey, | ||
systemImage: String, | ||
role: ButtonRole? = nil, | ||
behavior: Behavior = .blocking(showPlaceholder: true), | ||
action: @escaping @Sendable @MainActor () async -> Void, | ||
@ViewBuilder placeholder: @escaping () -> P = { ProgressView() } | ||
) where L == Label<Text, Image> { | ||
self.action = action | ||
self.label = { Label(text, systemImage: systemImage) } | ||
self.placeholder = placeholder | ||
self.role = role | ||
self.behavior = behavior | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
Sources/TaskTrigger/TaskTriggerButton/TaskTriggerButton.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// | ||
// TaskTriggerButton.swift | ||
// TaskTrigger | ||
// | ||
// Created by Lukas Pistrol on 26.11.24. | ||
// | ||
|
||
import SwiftUI | ||
|
||
/// A `Button` that triggers an async task that is bound to the button using a ``TaskTrigger``. | ||
/// | ||
/// > To get a better understanding of how a ``TaskTrigger`` works and its use cases, please refer to the | ||
/// > documentation of the ``TaskTrigger`` package. | ||
/// | ||
/// When the button is tapped the action is performed. Depending on the ``TaskTriggerButton/Behavior`` the button | ||
/// behaves differently: | ||
/// | ||
/// - `.blocking`: | ||
/// The button is disabled while the task is running. | ||
/// A placeholder can be shown while the task is running. | ||
/// - `.cancellable`: | ||
/// The button can be tapped again to cancel the task. | ||
/// A placeholder can be shown while the task is running. | ||
/// - `.restart`: | ||
/// The button can be tapped again to cancel and restart | ||
/// the task. A placeholder can be shown while the task is running. | ||
/// | ||
/// ```swift | ||
/// // ⛔ Bad: | ||
/// // Task will not be cancelled when the button is | ||
/// // removed from the view hierarchy or is tapped | ||
/// // multiple times. | ||
/// Button("Start Task") { | ||
/// Task { | ||
/// await someAsyncTask() | ||
/// } | ||
/// } | ||
/// | ||
/// // ✅ Good: | ||
/// TaskTriggerButton("Start Task") { | ||
/// await someAsyncTask() | ||
/// } | ||
/// ``` | ||
/// | ||
/// The default behavior is `.blocking` with a `ProgressView` as a placeholder. In all cases the task is bound to | ||
/// the ``TaskTriggerButton`` and will be cancelled as soon as the ``TaskTriggerButton`` is removed from the view | ||
/// hierarchy. | ||
public struct TaskTriggerButton<L: View, P: View>: View { | ||
|
||
let action: @Sendable @MainActor () async -> Void | ||
let label: () -> L | ||
let placeholder: () -> P | ||
let role: ButtonRole? | ||
let behavior: Behavior | ||
|
||
/// Creates a new ``TaskTriggerButton`` with a label. | ||
/// - Parameters: | ||
/// - role: The button's role. Defaults to `nil`. | ||
/// - behavior: The button's behavior. Defaults to `.blocking(showPlaceholder: true)`. | ||
/// - action: The async action to perform when the button is tapped. | ||
/// - label: The button's label. This can be any `View`. | ||
/// - placeholder: The placeholder to show while the task is running. Defaults to a `ProgressView`. | ||
/// If `showPlaceholder` on the `role` is set to `false`, this is ignored. | ||
public init( | ||
role: ButtonRole? = nil, | ||
behavior: Behavior = .blocking(showPlaceholder: true), | ||
action: @escaping @Sendable @MainActor () async -> Void, | ||
@ViewBuilder label: @escaping () -> L, | ||
@ViewBuilder placeholder: @escaping () -> P = { ProgressView() } | ||
) { | ||
self.action = action | ||
self.label = label | ||
self.placeholder = placeholder | ||
self.role = role | ||
self.behavior = behavior | ||
} | ||
|
||
@State private var trigger = PlainTaskTrigger() | ||
|
||
public var body: some View { | ||
Button(role: role) { | ||
switch trigger.state { | ||
case .none: | ||
trigger.trigger() | ||
case .active: | ||
if case .cancellable = behavior { | ||
trigger.cancel() | ||
} else if case .restart = behavior { | ||
trigger.trigger() | ||
} | ||
} | ||
} label: { | ||
switch trigger.state { | ||
case .none: | ||
label() | ||
case .active: | ||
if behavior.showPlaceholder { | ||
placeholder() | ||
} else { | ||
label() | ||
} | ||
} | ||
} | ||
.disabled(behavior.isBlocking && trigger.state != .none) | ||
.task($trigger, action) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.