Compare commits
1 Commits
main
...
text-field
Author | SHA1 | Date |
---|---|---|
![]() |
b42e4dbaea |
|
@ -0,0 +1,71 @@
|
|||
import SwiftUI
|
||||
|
||||
public protocol ActionState {
|
||||
associatedtype Action
|
||||
associatedtype Body: View
|
||||
|
||||
func body(withAction perform: @escaping (Action) -> Void) -> Body
|
||||
}
|
||||
|
||||
public struct AnyActionState<Action>: ActionState {
|
||||
public typealias Action = Action
|
||||
|
||||
public typealias Body = AnyView
|
||||
|
||||
private let _body: (@escaping (Action) -> Void) -> AnyView
|
||||
|
||||
public init<S: ActionState>(_ state: S) where S.Action == Action {
|
||||
self._body = { perform in
|
||||
AnyView(state.body(withAction: perform))
|
||||
}
|
||||
}
|
||||
|
||||
public func body(withAction perform: @escaping (Action) -> Void) -> AnyView {
|
||||
self._body(perform)
|
||||
}
|
||||
}
|
||||
|
||||
@resultBuilder
|
||||
public enum ActionStateBuilder<Action> {
|
||||
public static func buildArray(
|
||||
_ components: [[AnyActionState<Action>]]
|
||||
) -> [AnyActionState<Action>] {
|
||||
components.flatMap { $0 }
|
||||
}
|
||||
|
||||
public static func buildBlock(
|
||||
_ components: [AnyActionState<Action>]...
|
||||
) -> [AnyActionState<Action>] {
|
||||
components.flatMap { $0 }
|
||||
}
|
||||
|
||||
public static func buildLimitedAvailability(
|
||||
_ component: [AnyActionState<Action>]
|
||||
) -> [AnyActionState<Action>] {
|
||||
component
|
||||
}
|
||||
|
||||
public static func buildEither(
|
||||
first component: [AnyActionState<Action>]
|
||||
) -> [AnyActionState<Action>] {
|
||||
component
|
||||
}
|
||||
|
||||
public static func buildEither(
|
||||
second component: [AnyActionState<Action>]
|
||||
) -> [AnyActionState<Action>] {
|
||||
component
|
||||
}
|
||||
|
||||
public static func buildExpression(
|
||||
_ expression: AnyActionState<Action>
|
||||
) -> [AnyActionState<Action>] {
|
||||
[expression]
|
||||
}
|
||||
|
||||
public static func buildOptional(
|
||||
_ component: [AnyActionState<Action>]?
|
||||
) -> [AnyActionState<Action>] {
|
||||
component ?? []
|
||||
}
|
||||
}
|
|
@ -93,6 +93,13 @@ public struct ButtonState<Action>: Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
|
||||
extension ButtonState: ActionState {
|
||||
public func body(withAction perform: @escaping (Action) -> Void) -> some View {
|
||||
Button(self, action: perform)
|
||||
}
|
||||
}
|
||||
|
||||
extension ButtonState: CustomDumpReflectable {
|
||||
public var customDumpMirror: Mirror {
|
||||
var children: [(label: String?, value: Any)] = []
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import SwiftUI
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct MyView: View {
|
||||
enum AlertAction {
|
||||
case usernameChanged(String)
|
||||
case passwordChanged(String)
|
||||
case loginButtonTapped
|
||||
}
|
||||
|
||||
@State var alert: AlertState<AlertAction>?
|
||||
@State var username = ""
|
||||
@State var password = ""
|
||||
|
||||
var body: some View {
|
||||
Button("Tap") {
|
||||
self.alert = AlertState {
|
||||
TextState("Log in")
|
||||
} actions: {
|
||||
TextFieldState(action: /AlertAction.usernameChanged) {
|
||||
Text("blob@pointfree.co")
|
||||
}
|
||||
SecureFieldState(action: /AlertAction.passwordChanged) {
|
||||
Text("••••••••")
|
||||
}
|
||||
ButtonState("")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
.alert(
|
||||
"Authentication required",
|
||||
isPresented: Binding(
|
||||
get: { self.alert != nil },
|
||||
set: { isPresented in
|
||||
if !isPresented {
|
||||
self.alert = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
) {
|
||||
|
||||
} message: {
|
||||
Text("Please enter your login details.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import CasePaths
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 16, macOS 13, tvOS 16, watchOS 8, *)
|
||||
public struct TextFieldState<Action> {
|
||||
public let initialText: String
|
||||
public let action: CasePath<Action, String>
|
||||
public let label: TextState
|
||||
|
||||
public init(
|
||||
initialText: String = "",
|
||||
action: CasePath<Action, String>,
|
||||
label: () -> TextState
|
||||
) {
|
||||
self.initialText = initialText
|
||||
self.action = action
|
||||
self.label = label()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16, macOS 13, tvOS 16, watchOS 8, *)
|
||||
extension TextFieldState: ActionState {
|
||||
public typealias Value = String
|
||||
|
||||
public func body(withAction perform: @escaping (Action) -> Void) -> some View {
|
||||
TextField(self, action: perform)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16, macOS 13, tvOS 16, watchOS 8, *)
|
||||
extension TextField where Label == Text {
|
||||
public init<Action>(_ state: TextFieldState<Action>, action: @escaping (Action) -> Void) {
|
||||
var text = state.initialText
|
||||
self.init(
|
||||
text: Binding(
|
||||
get: { text },
|
||||
set: { newText in
|
||||
text = newText
|
||||
action(state.action.embed(newText))
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(state.label)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue