Skip to content

Instantly share code, notes, and snippets.

@tclementdev
Last active February 23, 2023 21:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tclementdev/b8aae836e35957c472652549a9554632 to your computer and use it in GitHub Desktop.
Save tclementdev/b8aae836e35957c472652549a9554632 to your computer and use it in GitHub Desktop.
SwiftUI actions dispatcher
import SwiftUI
// NOTE: This is still unsatisfying as it does not support calling async, throwing and non-void returning actions.
// I'm still looking for something better, see another approach here:
// V2 -> https://gist.github.com/tclementdev/c402bf5fddd8570287874333bc45a474
struct ActionsDispatcher {
private struct Key: Hashable {
let action: Action
let objectIdentifier: ObjectIdentifier
}
private var actions: [Key: Any] = [:]
subscript<T>(action: Action, for type: T.Type) -> (T) -> Void {
get {
let key = Key(action: action, objectIdentifier: ObjectIdentifier(T.self))
guard let action = actions[key] else {
fatalError("Missing handler for (\(action), \(T.self)).")
}
return action as! (T) -> Void
}
set {
let key = Key(action: action, objectIdentifier: ObjectIdentifier(T.self))
actions[key] = newValue
}
}
func callAsFunction<T>(_ action: Action, value: T) {
let closure = self[action, for: T.self]
closure(value)
}
}
struct ActionsDispatcherKey: EnvironmentKey {
static let defaultValue = ActionsDispatcher()
}
extension EnvironmentValues {
var dispatch: ActionsDispatcher {
get { self[ActionsDispatcherKey.self] }
set { self[ActionsDispatcherKey.self] = newValue }
}
}
extension View {
func action<T>(_ action: Action, for type: T.Type, _ handler: @escaping (T) -> Void) -> some View {
transformEnvironment(\.dispatch) { dispatch in
dispatch[action, for: T.self] = handler
}
}
}
struct Document {
let name: String
}
final class Controller: ObservableObject {
func create(_ document: Document) {
}
func send(_ document: Document) {
}
}
enum Action {
case create
case send
}
struct ContentView: View {
@StateObject var controller = Controller()
var body: some View {
SubView()
.action(.create, for: Document.self) { document in
controller.create(document)
}
.action(.send, for: Document.self) { document in
controller.send(document)
}
}
}
struct SubView: View {
@Environment(\.dispatch) var dispatch
var body: some View {
Button("create") {
let document = Document(name: "doc1")
dispatch(.create, value: document)
}
Button("send") {
let document = Document(name: "doc2")
dispatch(.send, value: document)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment