Skip to content

Commit

Permalink
Merge pull request #7 from treastrain/async-text-field-link
Browse files Browse the repository at this point in the history
Add `AsyncTextFieldLink`
  • Loading branch information
treastrain authored Nov 4, 2023
2 parents aa69a8f + f7090df commit 4295308
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 33 deletions.
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,26 @@ Finally, add `import AsyncSwiftUI` to your source code (or replace the existing

### Controls and indicators

| SwiftUI | AsyncSwiftUI | Available on |
|:-----------------------------------------------------------------------------------------------------|:--------------|:--------------------------------------------------------------------------------------------------|
| [`Button`](https://developer.apple.com/documentation/swiftui/button) | `AsyncButton` | iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+, visionOS 1.0+ |
| [`EditButton`](https://developer.apple.com/documentation/swiftui/editbutton) | (No need) ||
| [`PasteButton`](https://developer.apple.com/documentation/swiftui/pastebutton) | 🚧 |<!-- iOS 16.0+, iPadOS 16.0+, macOS 12.0+, Mac Catalyst 16.0+, visionOS 1.0+ --> |
| [`RenameButton`](https://developer.apple.com/documentation/swiftui/renamebutton) | 🚧 |<!-- iOS 16.0+, iPadOS 16.0+, macOS 13.0+, Mac Catalyst 16.0+, tvOS 16.0+, watchOS 9.0+, visionOS 1.0+ --> |
| [`Link`](https://developer.apple.com/documentation/swiftui/link) | (No need) ||
| [`ShareLink`](https://developer.apple.com/documentation/swiftui/sharelink) | (No need) ||
| [`TextFieldLink`](https://developer.apple.com/documentation/swiftui/textfieldlink) | 🚧 |<!-- watchOS 9.0+ --> |
| [`HelpLink`](https://developer.apple.com/documentation/swiftui/helplink) | 🚧 |<!-- macOS 14.0+, visionOS 1.0+ --> |
| [`Slider`](https://developer.apple.com/documentation/swiftui/slider) | 🚧 |<!-- iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, watchOS 8.0+, visionOS 1.0+ --> |
| [`Stepper`](https://developer.apple.com/documentation/swiftui/stepper) | 🚧 |<!-- iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, watchOS 9.0+, visionOS 1.0+ --> |
| [`Toggle`](https://developer.apple.com/documentation/swiftui/toggle) | 🚧 |<!-- iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+, visionOS 1.0+ --> |
| [`Picker`](https://developer.apple.com/documentation/swiftui/picker) | (No need) ||
| [`DatePicker`](https://developer.apple.com/documentation/swiftui/datepicker) | (No need) ||
| [`MultiDatePicker`](https://developer.apple.com/documentation/swiftui/multidatepicker) | (No need) ||
| [`ColorPicker`](https://developer.apple.com/documentation/swiftui/colorpicker) | (No need) ||
| [`Gauge`](https://developer.apple.com/documentation/swiftui/gauge) | (No need) ||
| [`ProgressView`](https://developer.apple.com/documentation/swiftui/progressview) | (No need) ||
| [`ContentUnavailableView`](https://developer.apple.com/documentation/swiftui/contentunavailableview) | (No need) ||
| SwiftUI | AsyncSwiftUI | Available on |
|:-----------------------------------------------------------------------------------------------------|:---------------------|:--------------------------------------------------------------------------------------------------|
| [`Button`](https://developer.apple.com/documentation/swiftui/button) | `AsyncButton` | iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+, visionOS 1.0+ |
| [`EditButton`](https://developer.apple.com/documentation/swiftui/editbutton) | (No need) ||
| [`PasteButton`](https://developer.apple.com/documentation/swiftui/pastebutton) | 🚧 |<!-- iOS 16.0+, iPadOS 16.0+, macOS 12.0+, Mac Catalyst 16.0+, visionOS 1.0+ --> |
| [`RenameButton`](https://developer.apple.com/documentation/swiftui/renamebutton) | 🚧 |<!-- iOS 16.0+, iPadOS 16.0+, macOS 13.0+, Mac Catalyst 16.0+, tvOS 16.0+, watchOS 9.0+, visionOS 1.0+ --> |
| [`Link`](https://developer.apple.com/documentation/swiftui/link) | (No need) ||
| [`ShareLink`](https://developer.apple.com/documentation/swiftui/sharelink) | (No need) ||
| [`TextFieldLink`](https://developer.apple.com/documentation/swiftui/textfieldlink) | `AsyncTextFieldLink` | watchOS 9.0+ |
| [`HelpLink`](https://developer.apple.com/documentation/swiftui/helplink) | 🚧 |<!-- macOS 14.0+, visionOS 1.0+ --> |
| [`Slider`](https://developer.apple.com/documentation/swiftui/slider) | 🚧 |<!-- iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, watchOS 8.0+, visionOS 1.0+ --> |
| [`Stepper`](https://developer.apple.com/documentation/swiftui/stepper) | 🚧 |<!-- iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, watchOS 9.0+, visionOS 1.0+ --> |
| [`Toggle`](https://developer.apple.com/documentation/swiftui/toggle) | 🚧 |<!-- iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+, visionOS 1.0+ --> |
| [`Picker`](https://developer.apple.com/documentation/swiftui/picker) | (No need) ||
| [`DatePicker`](https://developer.apple.com/documentation/swiftui/datepicker) | (No need) ||
| [`MultiDatePicker`](https://developer.apple.com/documentation/swiftui/multidatepicker) | (No need) ||
| [`ColorPicker`](https://developer.apple.com/documentation/swiftui/colorpicker) | (No need) ||
| [`Gauge`](https://developer.apple.com/documentation/swiftui/gauge) | (No need) ||
| [`ProgressView`](https://developer.apple.com/documentation/swiftui/progressview) | (No need) ||
| [`ContentUnavailableView`](https://developer.apple.com/documentation/swiftui/contentunavailableview) | (No need) ||

### Menus and commands

Expand Down
18 changes: 9 additions & 9 deletions Sources/Control/AsyncButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftUI

/// A control that initiates an action.
/// - SeeAlso: [`SwiftUI.Button`](https://developer.apple.com/documentation/swiftui/button)
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
public struct AsyncButton<Label: View>: AsyncControl {
public typealias Base = Button<Label>
package typealias Value = ()
Expand Down Expand Up @@ -43,7 +43,7 @@ public struct AsyncButton<Label: View>: AsyncControl {
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
extension AsyncButton {
/// Creates a button that displays a custom label.
///
Expand All @@ -66,7 +66,7 @@ extension AsyncButton {
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
extension AsyncButton where Label == Text {
/// Creates a button that generates its label from a localized string key.
/// - Parameters:
Expand Down Expand Up @@ -108,7 +108,7 @@ extension AsyncButton where Label == Text {
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
extension AsyncButton where Label == SwiftUI.Label<Text, Image> {
/// Creates a button that generates its label from a localized string key and system image name.
/// - Parameters:
Expand Down Expand Up @@ -154,7 +154,7 @@ extension AsyncButton where Label == SwiftUI.Label<Text, Image> {
}
}

@available(iOS 17.0, macOS 14.0, macCatalyst 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
extension AsyncButton where Label == SwiftUI.Label<Text, Image> {
/// Creates a button that generates its label from a localized string key and image resource.
/// - Parameters:
Expand Down Expand Up @@ -200,7 +200,7 @@ extension AsyncButton where Label == SwiftUI.Label<Text, Image> {
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
extension AsyncButton {
/// Creates a button with a specified role that displays a custom label.
/// - Parameters:
Expand All @@ -224,7 +224,7 @@ extension AsyncButton {
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
extension AsyncButton where Label == Text {
/// Creates a button with a specified role that generates its label from a localized string key.
/// - Parameters:
Expand Down Expand Up @@ -270,7 +270,7 @@ extension AsyncButton where Label == Text {
}
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
extension AsyncButton where Label == SwiftUI.Label<Text, Image> {
/// Creates a button with a specified role that generates its label from a localized string key and a system image.
/// - Parameters:
Expand Down Expand Up @@ -320,7 +320,7 @@ extension AsyncButton where Label == SwiftUI.Label<Text, Image> {
}
}

@available(iOS 17.0, macOS 14.0, macCatalyst 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
extension AsyncButton where Label == SwiftUI.Label<Text, Image> {
/// Creates a button with a specified role that generates its label from a localized string key and an image resource.
/// - Parameters:
Expand Down
126 changes: 126 additions & 0 deletions Sources/Control/AsyncTextFieldLink.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//
// AsyncTextFieldLink.swift
// AsyncSwiftUI
//
// Created by treastrain on 2023/11/04.
//

import Core
import SwiftUI

/// A control that requests text input from the user when pressed.
@available(watchOS 9.0, *)
@available(iOS, unavailable)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(visionOS, unavailable)
public struct AsyncTextFieldLink<Label: View>: AsyncControl {
public typealias Base = TextFieldLink<Label>
package typealias Value = String

private let priority: TaskPriority
private let onSubmit: @Sendable (String) async -> Void
private let base: (_ trigger: _AsyncControlTrigger<Value>?) -> Base

@State private var trigger: _AsyncControlTrigger<Value>? = nil

private init(
priority: TaskPriority,
@_inheritActorContext onSubmit: @escaping @Sendable (String) async -> Void,
base: @escaping (_ trigger: _AsyncControlTrigger<Value>?) -> Base
) {
self.priority = priority
self.onSubmit = onSubmit
self.base = base
}

public var body: some View {
AsyncControlView(
priority: priority,
action: onSubmit,
base: base(trigger)
)
.onPreferenceChange(_AsyncControlTriggerPreferenceKey.self) {
trigger = $0
}
}
}

@available(watchOS 9.0, *)
@available(iOS, unavailable)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(visionOS, unavailable)
extension AsyncTextFieldLink {
/// Creates a TextFieldLink which when pressed will request text input from the user.
///
/// - Parameters:
/// - prompt: Text which describes the reason for requesting text input.
/// - label: A view that describes the action of requesting text input.
/// - priority: The task priority to use when creating the asynchronous task. The default priority is `userInitiated`.
/// - onSubmit: An action to perform when text input has been accepted and dismissed.
public init(
prompt: Text? = nil,
@ViewBuilder label: @escaping () -> Label,
priority: TaskPriority = .userInitiated,
@_inheritActorContext onSubmit: @escaping @Sendable (String) async -> Void
) {
self.init(
priority: priority,
onSubmit: onSubmit,
base: { trigger in
Base(prompt: prompt, label: label, onSubmit: { trigger?($0) })
}
)
}
}

@available(watchOS 9.0, *)
@available(iOS, unavailable)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(visionOS, unavailable)
extension AsyncTextFieldLink where Label == Text {
/// Creates a TextFieldLink which when pressed will request text input from the user.
/// - Parameters:
/// - titleKey: A key for the TextFieldLink's localized title, that describes the purpose of requesting text input.
/// - prompt: Text which describes the reason for requesting text input.
/// - priority: The task priority to use when creating the asynchronous task. The default priority is `userInitiated`.
/// - onSubmit: An action to perform when text input has been accepted and dismissed.
public init(
_ titleKey: LocalizedStringKey,
prompt: Text? = nil,
priority: TaskPriority = .userInitiated,
@_inheritActorContext onSubmit: @escaping @Sendable (String) async -> Void
) {
self.init(
priority: priority,
onSubmit: onSubmit,
base: { trigger in
Base(titleKey, prompt: prompt, onSubmit: { trigger?($0) })
}
)
}

/// Creates a TextFieldLink which when pressed will request text input from the user.
/// - Parameters:
/// - title: A string that describes the purpose of requesting text input.
/// - prompt: Text which describes the reason for requesting text input.
/// - priority: The task priority to use when creating the asynchronous task. The default priority is `userInitiated`.
/// - onSubmit: An action to perform when text input has been accepted and dismissed.
@_disfavoredOverload
public init<S>(
_ title: S,
prompt: Text? = nil,
priority: TaskPriority = .userInitiated,
@_inheritActorContext onSubmit: @escaping @Sendable (String) async -> Void
) where S : StringProtocol {
self.init(
priority: priority,
onSubmit: onSubmit,
base: { trigger in
Base(title, prompt: prompt, onSubmit: { trigger?($0) })
}
)
}
}
2 changes: 1 addition & 1 deletion Sources/Core/AsyncControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import SwiftUI

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
package protocol AsyncControl: View {
associatedtype Base: View
associatedtype Value
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/AsyncControlView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import SwiftUI

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
package struct AsyncControlView<Base: View, Value>: View {
private let priority: TaskPriority
private let action: @Sendable (_ value: Value) async -> Void
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/_AsyncControlTrigger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// - Warning: This `Equatable` compliance is a dummy implementation added for use in `SwiftUI.View.onPreferenceChange(_:perform:)`.
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
package struct _AsyncControlTrigger<Value>: Equatable {
private let action: (_ value: Value) -> Void

Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/_AsyncControlTriggerPreferenceKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import SwiftUI

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
package struct _AsyncControlTriggerPreferenceKey<Value>: PreferenceKey {
package static var defaultValue: _AsyncControlTrigger<Value> { fatalError("This property is not expected to be called.") }
package static func reduce(value: inout _AsyncControlTrigger<Value>, nextValue: () -> _AsyncControlTrigger<Value>) { fatalError("This function is not expected to be called.") }
Expand Down

0 comments on commit 4295308

Please sign in to comment.