|
27 | 27 | // renders a visible diagnostic rather than failing silently. |
28 | 28 | // |
29 | 29 |
|
| 30 | +/// A callback a custom composable can invoke to send an event back to Swift — |
| 31 | +/// the counterpart to a `UIViewRepresentable`'s coordinator. The associated |
| 32 | +/// value's type matches the fixed bridge dispatch surface. |
| 33 | +public enum ComposableAction { |
| 34 | + case void(() -> Void) |
| 35 | + case bool((Bool) -> Void) |
| 36 | + case double((Double) -> Void) |
| 37 | + case int((Int) -> Void) |
| 38 | + case string((String) -> Void) |
| 39 | + |
| 40 | + internal func register(in callbacks: CallbackRegistry) -> Int64 { |
| 41 | + switch self { |
| 42 | + case .void(let action): return callbacks.register(.void(action)) |
| 43 | + case .bool(let action): return callbacks.register(.bool(action)) |
| 44 | + case .double(let action): return callbacks.register(.double(action)) |
| 45 | + case .int(let action): return callbacks.register(.int(action)) |
| 46 | + case .string(let action): return callbacks.register(.string(action)) |
| 47 | + } |
| 48 | + } |
| 49 | +} |
| 50 | + |
30 | 51 | public struct ComposableView<Content: View>: View { |
31 | 52 |
|
32 | 53 | internal let name: String |
33 | 54 | internal let props: [String: PropValue] |
| 55 | + internal let actions: [String: ComposableAction] |
34 | 56 | internal let content: Content |
35 | 57 |
|
36 | | - public init(_ name: String, props: [String: PropValue] = [:], @ViewBuilder content: () -> Content) { |
| 58 | + public init( |
| 59 | + _ name: String, |
| 60 | + props: [String: PropValue] = [:], |
| 61 | + actions: [String: ComposableAction] = [:], |
| 62 | + @ViewBuilder content: () -> Content |
| 63 | + ) { |
37 | 64 | self.name = name |
38 | 65 | self.props = props |
| 66 | + self.actions = actions |
39 | 67 | self.content = content() |
40 | 68 | } |
41 | 69 |
|
42 | 70 | public typealias Body = Never |
43 | 71 | } |
44 | 72 |
|
45 | 73 | public extension ComposableView where Content == EmptyView { |
46 | | - init(_ name: String, props: [String: PropValue] = [:]) { |
47 | | - self.init(name, props: props) { EmptyView() } |
| 74 | + init(_ name: String, props: [String: PropValue] = [:], actions: [String: ComposableAction] = [:]) { |
| 75 | + self.init(name, props: props, actions: actions) { EmptyView() } |
48 | 76 | } |
49 | 77 | } |
50 | 78 |
|
51 | 79 | extension ComposableView: PrimitiveView { |
52 | 80 | public func _render(in context: ResolveContext) -> RenderNode { |
53 | 81 | var props = self.props |
54 | 82 | props["name"] = .string(name) // reserved: identifies the registered factory |
| 83 | + // Each action registers a callback; its id crosses as a prop the factory |
| 84 | + // reads back as a typed lambda. |
| 85 | + for (key, action) in actions { |
| 86 | + props[key] = .int(Int(action.register(in: context.callbacks))) |
| 87 | + } |
55 | 88 | return RenderNode( |
56 | 89 | type: "Composable", |
57 | 90 | id: context.path, |
|
0 commit comments