Skip to content

Commit

Permalink
Update model doc
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle-Ye committed Dec 17, 2023
1 parent 5832009 commit b950655
Show file tree
Hide file tree
Showing 13 changed files with 1,382 additions and 6 deletions.
157 changes: 157 additions & 0 deletions Sources/OpenSwiftUI/DataAndStorage/ModelData/Binding/Binding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,134 @@
// Lastest Version: iOS 15.5
// Status: Blocked by DynamicProperty

/// A property wrapper type that can read and write a value owned by a source of
/// truth.
///
/// Use a binding to create a two-way connection between a property that stores
/// data, and a view that displays and changes the data. A binding connects a
/// property to a source of truth stored elsewhere, instead of storing data
/// directly. For example, a button that toggles between play and pause can
/// create a binding to a property of its parent view using the `Binding`
/// property wrapper.
///
/// struct PlayButton: View {
/// @Binding var isPlaying: Bool
///
/// var body: some View {
/// Button(isPlaying ? "Pause" : "Play") {
/// isPlaying.toggle()
/// }
/// }
/// }
///
/// The parent view declares a property to hold the playing state, using the
/// ``State`` property wrapper to indicate that this property is the value's
/// source of truth.
///
/// struct PlayerView: View {
/// var episode: Episode
/// @State private var isPlaying: Bool = false
///
/// var body: some View {
/// VStack {
/// Text(episode.title)
/// .foregroundStyle(isPlaying ? .primary : .secondary)
/// PlayButton(isPlaying: $isPlaying) // Pass a binding.
/// }
/// }
/// }
///
/// When `PlayerView` initializes `PlayButton`, it passes a binding of its state
/// property into the button's binding property. Applying the `$` prefix to a
/// property wrapped value returns its ``State/projectedValue``, which for a
/// state property wrapper returns a binding to the value.
///
/// Whenever the user taps the `PlayButton`, the `PlayerView` updates its
/// `isPlaying` state.
///
/// > Note: To create bindings to properties of a type that conforms to the
/// [Observable](https://developer.apple.com/documentation/Observation/Observable)
/// protocol, use the ``Bindable`` property wrapper. For more information,
/// see <doc:Migrating-from-the-observable-object-protocol-to-the-observable-macro>.
@frozen
@propertyWrapper
@dynamicMemberLookup
public struct Binding<Value> {
/// The binding's transaction.
///
/// The transaction captures the information needed to update the view when
/// the binding value changes.
public var transaction: Transaction
var location: AnyLocation<Value>
private var _value: Value

/// Creates a binding with closures that read and write the binding value.
///
/// - Parameters:
/// - get: A closure that retrieves the binding value. The closure has no
/// parameters, and returns a value.
/// - set: A closure that sets the binding value. The closure has the
/// following parameter:
/// - newValue: The new value of the binding value.
public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) {
let location = FunctionalLocation(getValue: get) { value, _ in set(value) }
let box = LocationBox(location: location)
self.init(value: get(), location: box)
}

/// Creates a binding with a closure that reads from the binding value, and
/// a closure that applies a transaction when writing to the binding value.
///
/// - Parameters:
/// - get: A closure to retrieve the binding value. The closure has no
/// parameters, and returns a value.
/// - set: A closure to set the binding value. The closure has the
/// following parameters:
/// - newValue: The new value of the binding value.
/// - transaction: The transaction to apply when setting a new value.
public init(get: @escaping () -> Value, set: @escaping (Value, Transaction) -> Void) {
let location = FunctionalLocation(getValue: get, setValue: set)
let box = LocationBox(location: location)
self.init(value: get(), location: box)
}

/// Creates a binding with an immutable value.
///
/// Use this method to create a binding to a value that cannot change.
/// This can be useful when using a ``PreviewProvider`` to see how a view
/// represents different values.
///
/// // Example of binding to an immutable value.
/// PlayButton(isPlaying: Binding.constant(true))
///
/// - Parameter value: An immutable value.
public static func constant(_ value: Value) -> Binding<Value> {
let location = ConstantLocation(value: value)
let box = LocationBox(location: location)
return Binding(value: value, location: box)
}

/// The underlying value referenced by the binding variable.
///
/// This property provides primary access to the value's data. However, you
/// don't access `wrappedValue` directly. Instead, you use the property
/// variable created with the ``Binding`` attribute. In the
/// following code example, the binding variable `isPlaying` returns the
/// value of `wrappedValue`:
///
/// struct PlayButton: View {
/// @Binding var isPlaying: Bool
///
/// var body: some View {
/// Button(isPlaying ? "Pause" : "Play") {
/// isPlaying.toggle()
/// }
/// }
/// }
///
/// When a mutable binding value changes, the new value is immediately
/// available. However, updates to a view displaying the value happens
/// asynchronously, so the view may not show the change immediately.
public var wrappedValue: Value {
get {
readValue()
Expand All @@ -41,37 +143,81 @@ public struct Binding<Value> {
}
}

/// A projection of the binding value that returns a binding.
///
/// Use the projected value to pass a binding value down a view hierarchy.
/// To get the `projectedValue`, prefix the property variable with `$`. For
/// example, in the following code example `PlayerView` projects a binding
/// of the state property `isPlaying` to the `PlayButton` view using
/// `$isPlaying`.
///
/// struct PlayerView: View {
/// var episode: Episode
/// @State private var isPlaying: Bool = false
///
/// var body: some View {
/// VStack {
/// Text(episode.title)
/// .foregroundStyle(isPlaying ? .primary : .secondary)
/// PlayButton(isPlaying: $isPlaying)
/// }
/// }
/// }
///
public var projectedValue: Binding<Value> { self }

/// Creates a binding from the value of another binding.
@_alwaysEmitIntoClient
public init(projectedValue: Binding<Value>) {
self = projectedValue
}

/// Returns a binding to the resulting value of a given key path.
///
/// - Parameter keyPath: A key path to a specific resulting value.
///
/// - Returns: A new binding.
public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> {
projecting(keyPath)
}
}

extension Binding {
/// Creates a binding by projecting the base value to an optional value.
///
/// - Parameter base: A value to project to an optional value.
public init<V>(_ base: Binding<V>) where Value == V? {
self = base.projecting(BindingOperations.ToOptional())
}

/// Creates a binding by projecting the base value to an unwrapped value.
///
/// - Parameter base: A value to project to an unwrapped value.
///
/// - Returns: A new binding or `nil` when `base` is `nil`.
public init?(_ base: Binding<Value?>) {
guard let _ = base.wrappedValue else {
return nil
}
self = base.projecting(BindingOperations.ForceUnwrapping())
}

/// Creates a binding by projecting the base value to a hashable value.
///
/// - Parameters:
/// - base: A `Hashable` value to project to an `AnyHashable` value.
public init(_ base: Binding<some Hashable>) where Value == AnyHashable {
self = base.projecting(BindingOperations.ToAnyHashable())
}
}

extension Binding: Identifiable where Value: Identifiable {
/// The stable identity of the entity associated with this instance,
/// corresponding to the `id` of the binding's wrapped value.
public var id: Value.ID { wrappedValue.id }

/// A type representing the stable identity of the entity associated with
/// an instance.
public typealias ID = Value.ID
}

Expand Down Expand Up @@ -126,12 +272,23 @@ extension Binding: BidirectionalCollection where Value: BidirectionalCollection,
extension Binding: RandomAccessCollection where Value: MutableCollection, Value: RandomAccessCollection {}

extension Binding {
/// Specifies a transaction for the binding.
///
/// - Parameter transaction : An instance of a ``Transaction``.
///
/// - Returns: A new binding.
public func transaction(_ transaction: Transaction) -> Binding<Value> {
var binding = self
binding.transaction = transaction
return binding
}

/// Specifies an animation to perform when the binding value changes.
///
/// - Parameter animation: An animation sequence performed when the binding
/// value changes.
///
/// - Returns: A new binding.
public func animation(_ animation: Animation? = .default) -> Binding<Value> {
var binding = self
binding.transaction.animation = animation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public protocol DynamicProperty {
static var _propertyBehaviors: UInt32 { get }
/// Updates the underlying value of the stored value.
///
/// SwiftUI calls this function before rendering a view's
/// OpenSwiftUI calls this function before rendering a view's
/// ``View/body-swift.property`` to ensure the view has the most recent
/// value.
mutating func update()
Expand Down
Loading

0 comments on commit b950655

Please sign in to comment.