Compare commits

...

1 Commits

Author SHA1 Message Date
Stephen Celis b42e4dbaea wip 2022-11-24 09:30:50 -06:00
3 changed files with 173 additions and 0 deletions

View File

@ -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 ?? []
}
}

View File

@ -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)] = []

View File

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