1
0

Binding.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /// A value that can read and write a value owned by a source of truth.
  2. ///
  3. /// You can create a binding in several different ways:
  4. /// - by accessing the ``projectedValue`` (via leading `$` syntax) on a piece of
  5. /// ``State`` or another `Binding`:
  6. /// ```swift
  7. /// @State var name = "John Appleseed"
  8. /// TextField("Name", text: $name)
  9. /// ```
  10. /// - by _projecting_ a property of an existing binding with dynamic member
  11. /// lookup (``subscript(dynamicMember:)``):
  12. /// ```swift
  13. /// @Binding var account: Account
  14. /// TextField("Email", text: $account.email)
  15. /// Toggle("Notifications", isOn: $account.notificationsEnabled)
  16. /// ```
  17. /// - by calling ``init(get:set:)`` with a custom getter and setter:
  18. /// ```swift
  19. /// let binding = Binding(
  20. /// get: { endpoint.getData() },
  21. /// set: { endpoint.setData(to: $0) }
  22. /// )
  23. /// ```
  24. ///
  25. /// That last one reveals something important about bindings: while they can be
  26. /// thought of as writable references to their sources of truth, in reality
  27. /// they're nothing more than getter-setter pairs. A binding can have any
  28. /// arbitrary getter and setter, and the two functions don't even have to be
  29. /// related. However, SwiftCrossUI's reactivity relies on a binding's getter
  30. /// and setter acting in a consistent manner; see ``init(get:set:)`` for more
  31. /// info.
  32. @dynamicMemberLookup
  33. @propertyWrapper
  34. public struct Binding<Value> {
  35. /// The binding's wrapped value.
  36. public var wrappedValue: Value {
  37. get {
  38. getValue()
  39. }
  40. nonmutating set {
  41. setValue(newValue)
  42. }
  43. }
  44. /// The binding itself.
  45. ///
  46. /// This is a handy helper so that you can use ``Binding`` properties like
  47. /// you would with ``State`` properties.
  48. public var projectedValue: Binding<Value> {
  49. self
  50. }
  51. /// The stored getter.
  52. private let getValue: () -> Value
  53. /// The stored setter.
  54. private let setValue: (Value) -> Void
  55. /// Creates a binding with a custom getter and setter.
  56. ///
  57. /// To create a binding from a ``State`` property, use its projected value
  58. /// instead: e.g. `$myStateProperty` will give you a binding for reading and
  59. /// writing `myStateProperty`.
  60. ///
  61. /// - Important: SwiftCrossUI's reactivity relies on a binding's getter and
  62. /// setter consistently reading and updating the same source of truth ---
  63. /// calling `get` immediately after calling `set` should always return the
  64. /// same value (barring data races). Views will not update as you expect
  65. /// if you break this assumption.
  66. ///
  67. /// - Parameters:
  68. /// - get: The binding's getter.
  69. /// - set: The binding's setter.
  70. public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) {
  71. self.getValue = get
  72. self.setValue = set
  73. }
  74. /// Converts a `Binding<Value?>` into a `Binding<Value>?`, returning `nil`
  75. /// if the `wrappedValue` of `other` is `nil`.
  76. ///
  77. /// - Parameter other: A binding with an optional value.
  78. /// - Returns: An optional binding with a non-optional value.
  79. public init?(_ other: Binding<Value?>) {
  80. if let initialValue = other.wrappedValue {
  81. self.init(
  82. get: {
  83. other.wrappedValue ?? initialValue
  84. },
  85. set: { newValue in
  86. other.wrappedValue = newValue
  87. }
  88. )
  89. } else {
  90. return nil
  91. }
  92. }
  93. /// Projects a property of a binding.
  94. ///
  95. /// - Parameter keyPath: A key path from this binding's value type.
  96. /// - Returns: A binding to the property referenced by `keyPath`.
  97. public subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> Binding<T> {
  98. get {
  99. Binding<T>(
  100. get: {
  101. self.wrappedValue[keyPath: keyPath]
  102. },
  103. set: { newValue in
  104. self.wrappedValue[keyPath: keyPath] = newValue
  105. }
  106. )
  107. }
  108. }
  109. /// Returns a new binding that will perform an action whenever it is used to set
  110. /// the source of truth's value.
  111. ///
  112. /// - Parameter action: The action to perform.
  113. /// - Returns: A binding that calls `action` with the new value after
  114. /// setting it.
  115. public func onChange(_ action: @escaping (Value) -> Void) -> Binding<Value> {
  116. return Binding<Value>(
  117. get: getValue,
  118. set: { newValue in
  119. self.setValue(newValue)
  120. action(newValue)
  121. }
  122. )
  123. }
  124. }