67 lines
2.6 KiB
Swift
67 lines
2.6 KiB
Swift
import Observation
|
|
|
|
/// A property wrapper that vends two-way ``Binding``s to the properties of an
|
|
/// `@Observable` reference model.
|
|
///
|
|
/// `@Environment(Model.self)` hands a view a read-only model reference, and
|
|
/// ``Binding/init(_:_:)`` already turns one of that model's properties into a
|
|
/// two-way binding. `@Bindable` is the ergonomic front end for the pairing:
|
|
/// `$model.title` resolves, through `@dynamicMemberLookup`, to exactly
|
|
/// `Binding(model, \.title)`.
|
|
///
|
|
/// ```swift
|
|
/// struct Editor: View {
|
|
/// @Bindable var model: Model
|
|
///
|
|
/// var body: some View {
|
|
/// VStack {
|
|
/// SwitchRow().active($model.isEnabled)
|
|
/// Label(str: "").label($model.title)
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// The wrapper stores the model reference and nothing else: no state, no
|
|
/// subscription, and no ``DynamicProperty`` conformance, so the mount-time
|
|
/// `_resolveDynamicProperties` walk skips it. All reactivity belongs to the
|
|
/// ``Binding`` a subscript vends, which routes through ``DependencyTracker``
|
|
/// and ``ObservationBridge`` exactly like a hand-written observable binding -
|
|
/// widget edits reach the model synchronously, model mutations reach widgets on
|
|
/// the coalesced idle flush.
|
|
@propertyWrapper @dynamicMemberLookup @MainActor
|
|
public struct Bindable<Value: AnyObject & Observation.Observable> {
|
|
/// The wrapped model.
|
|
///
|
|
/// Read-only: `@Bindable` never re-points at a different object, it only
|
|
/// vends bindings into the one it was given.
|
|
public let wrappedValue: Value
|
|
|
|
/// Wraps `wrappedValue`, backing `@Bindable var model: Model` declarations.
|
|
///
|
|
/// - Parameter wrappedValue: The observable model. Held strongly.
|
|
public init(wrappedValue: Value) {
|
|
self.wrappedValue = wrappedValue
|
|
}
|
|
|
|
/// Wraps `object` for inline use, as in `Bindable(model).title`.
|
|
///
|
|
/// - Parameter object: The observable model. Held strongly.
|
|
public init(_ object: Value) {
|
|
self.wrappedValue = object
|
|
}
|
|
|
|
/// Self, so that `$model.property` reaches ``subscript(dynamicMember:)``.
|
|
public var projectedValue: Bindable<Value> { self }
|
|
|
|
/// A two-way ``Binding`` to the model property named by `keyPath`.
|
|
///
|
|
/// - Parameter keyPath: A writable key path into the wrapped model.
|
|
/// - Returns: A binding whose reads track and whose writes land directly on
|
|
/// the model, propagating to every other reader via Observation.
|
|
public subscript<Subject>(
|
|
dynamicMember keyPath: ReferenceWritableKeyPath<Value, Subject>
|
|
) -> Binding<Subject> {
|
|
Binding(wrappedValue, keyPath)
|
|
}
|
|
}
|