Last active
February 23, 2023 21:29
-
-
Save tclementdev/b8aae836e35957c472652549a9554632 to your computer and use it in GitHub Desktop.
SwiftUI actions dispatcher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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