Skip to content

Instantly share code, notes, and snippets.

@JadenGeller
Last active October 26, 2023 20:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JadenGeller/ede4c402a0228ebc73093eaccfe600ed to your computer and use it in GitHub Desktop.
Save JadenGeller/ede4c402a0228ebc73093eaccfe600ed to your computer and use it in GitHub Desktop.
SwiftUI action handling, similar to KeyPress but for generic actions
import SwiftUI
struct ActionDispatch<Action, Content: View>: View {
@ViewBuilder var content: (@escaping (Action) -> ActionResult) -> Content
@State var handler: ActionHandler<Action> = .unhandled
// ^use @Box here instead of @State here to avoid the extra re-rendering: https://gist.github.com/JadenGeller/b6ce5db2470aeabcf2f8e936cb3a5725
init(_ type: Action.Type, content: @escaping (@escaping (Action) -> ActionResult) -> Content) {
self.content = content
}
var body: some View {
content { action in
handler.handle(action)
}
.onPreferenceChange(ActionHandlerPreferenceKey<Action>.self) { handler = $0 }
}
}
extension View {
func actionHandler<Action>(_ type: Action.Type, handler: @escaping (Action) -> ActionResult) -> some View {
preference(key: ActionHandlerPreferenceKey<Action>.self, value: .handler(UUID(), handler))
}
}
enum ActionHandler<Action>: Equatable {
case unhandled
case handler(UUID, (Action) -> ActionResult)
indirect case reduced(Self, Self)
static func ==(lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.unhandled, .unhandled): true
case (.unhandled, _), (_, .unhandled): false
case (.handler(let lid, _), .handler(let rid, _)): lid == rid
case (.handler, _), (_, .handler): false
case (.reduced(let llh, let lrh), .reduced(let rlh, let rrh)): llh == rlh && lrh == rrh
case (.reduced, _), (_, .reduced): false
}
}
func handle(_ action: Action) -> ActionResult {
switch self {
case .unhandled: .ignored
case .handler(_, let handle): handle(action)
case .reduced(let prev, let next):
switch prev.handle(action) {
case .handled: .handled
case .ignored: next.handle(action)
default: preconditionFailure("unexpected action result")
}
}
}
}
typealias ActionResult = KeyPress.Result
struct ActionHandlerPreferenceKey<Action>: PreferenceKey {
static var defaultValue: ActionHandler<Action> { .unhandled }
static func reduce(value: inout ActionHandler<Action>, nextValue: () -> ActionHandler<Action>) {
value = .reduced(value, nextValue())
}
}
@JadenGeller
Copy link
Author

honestly it's probably better to just pass in a Binding to a Model or something tho, idk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment