TM-SGNL-iOS/SignalUI/SwiftUIExtensions/AsyncViewTask.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

79 lines
2.4 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
public import SwiftUI
// MARK: - AsyncViewTask
/// Represents an async operation that can be associated with the lifetime of
/// a view using the `task(_:)` view modifier.
public protocol AsyncViewTask: Identifiable {
/// Optionally provide a custom priority for the task. By default, the task
/// will be executed with the `.userInitied` priority.
/// See the `task(id:priority:_:)` view modifier for more information.
var priority: TaskPriority? { get }
/// The asynchronous action performed by the task.
func perform() async
}
extension AsyncViewTask {
public var priority: TaskPriority? { nil }
}
// MARK: - AsyncViewTaskModifier
extension View {
/// Associates a binding to an `AsyncViewTask` with the lifetime of a view.
///
/// When the binding is `nil`, the task is not executing. To begin the task
/// set the value of the binding to an instance of the `AsyncTask` type.
///
/// Buttons and other controls are automatically disabled while the task is
/// executing.
///
/// To cancel the active task, set the value of the binding to `nil` or a
/// new task value. The previous task will automatically be cancelled before
/// a new task begins.
///
/// ```swift
/// struct Nap: AsyncViewTask {
/// let id = UUID()
/// var duration: ContinousClock.Duration
///
/// func perform() async {
/// try? await Task.sleep(for: duration)
/// }
/// }
///
/// struct NappingButton: View {
/// @State private var nap: Nap?
///
/// var body: some View {
/// Button("Take a Nap") {
/// nap = Nap(duration: .seconds(60))
/// }
/// .task($nap.animation())
/// }
/// }
/// ```
public func task<AsyncTask: AsyncViewTask>(_ task: Binding<AsyncTask?>) -> some View {
modifier(AsyncViewTaskModifier(task: task))
}
}
public struct AsyncViewTaskModifier<Task: AsyncViewTask>: ViewModifier {
@Binding var task: Task?
public func body(content: Content) -> some View {
content
.disabled(task != nil)
.task(id: task?.id, priority: task?.priority ?? .userInitiated) {
guard let currentTask = task else { return }
defer { task = nil }
await currentTask.perform()
}
}
}