-
Notifications
You must be signed in to change notification settings - Fork 102
/
UIBindable.swift
179 lines (168 loc) · 5.05 KB
/
UIBindable.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import Perception
#if canImport(Observation)
import Observation
#endif
/// A property wrapper type that supports creating bindings to the mutable properties of observable
/// objects.
///
/// Like SwiftUI's `Bindable`, but for UIKit and other paradigms.
///
/// Use this property wrapper to create bindings to mutable properties of a data model object that
/// conforms to the `Observable` or `Perceptible` protocols. For example, the following code wraps
/// the book input with `@UIBindable`. Then it uses a `UITextField` to change the title property of
/// a book, and a `UISwitch` to change the `isAvailable` property, using the `$` syntax to pass a
/// binding for each property to those controls.
///
/// ```swift
/// @Observable
/// class Book: Identifiable {
/// var title = "Sample Book Title"
/// var isAvailable = true
/// }
///
///
/// final class BookEditViewController: UIViewController {
/// @Bindable var book: Book
///
/// // ...
///
/// func viewDidLoad() {
/// super.viewDidLoad()
///
/// let titleTextField = UITextField(text: $book.title)
/// let isAvailableSwitch = UISwitch(isOn: $book.isAvailable)
///
/// // Configure and add subviews...
/// }
/// }
/// ```
///
/// You can use the `UIBindable` property wrapper on properties and variables to an `Observable` (or
/// `Perceptible`) object. This includes global variables, properties that exists outside of SwiftUI
/// types, or even local variables.
@dynamicMemberLookup
@propertyWrapper
public struct UIBindable<Value> {
public var wrappedValue: Value
private let file: StaticString
private let fileID: StaticString
private let line: UInt
init(
objectIdentifier: ObjectIdentifier,
wrappedValue: Value,
file: StaticString,
fileID: StaticString,
line: UInt
) {
self.wrappedValue = wrappedValue
self.file = file
self.fileID = fileID
self.line = line
}
/// Creates a bindable object from a perceptible object.
///
/// This initializer is equivalent to `init(wrappedValue:)`, but is more succinct when when
/// creating bindable objects nested within other expressions.
@_disfavoredOverload
public init(
_ wrappedValue: Value,
file: StaticString = #file,
fileID: StaticString = #fileID,
line: UInt = #line
) where Value: AnyObject & Perceptible {
self.init(
objectIdentifier: ObjectIdentifier(wrappedValue),
wrappedValue: wrappedValue,
file: file,
fileID: fileID,
line: line
)
}
/// Creates a bindable object from a perceptible object.
///
/// You should not call this initializer directly. Instead, declare a property with the
/// `@UIBindable` attribute, and provide an initial value.
@_disfavoredOverload
public init(
wrappedValue: Value,
file: StaticString = #file,
fileID: StaticString = #fileID,
line: UInt = #line
) where Value: AnyObject & Perceptible {
self.init(
objectIdentifier: ObjectIdentifier(wrappedValue),
wrappedValue: wrappedValue,
file: file,
fileID: fileID,
line: line
)
}
/// Creates a bindable from the value of another bindable.
public init(projectedValue: Self) {
self = projectedValue
}
/// The bindable wrapper for the object that creates bindings to its properties using dynamic
/// member lookup.
public var projectedValue: Self {
self
}
/// Returns a binding to the value of a given key path.
public subscript<Member>(
dynamicMember keyPath: ReferenceWritableKeyPath<Value, Member>
) -> UIBinding<Member> where Value: AnyObject {
UIBinding(
root: wrappedValue,
keyPath: keyPath,
transaction: UITransaction(),
file: file,
fileID: fileID,
line: line
)
}
}
#if canImport(Observation)
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension UIBindable where Value: AnyObject & Observable {
/// Creates a bindable object from an observable object.
///
/// This initializer is equivalent to `init(wrappedValue:)`, but is more succinct when when
/// creating bindable objects nested within other expressions.
public init(
_ wrappedValue: Value,
file: StaticString = #file,
fileID: StaticString = #fileID,
line: UInt = #line
) {
self.init(
objectIdentifier: ObjectIdentifier(wrappedValue),
wrappedValue: wrappedValue,
file: file,
fileID: fileID,
line: line
)
}
/// Creates a bindable object from an observable object.
///
/// You should not call this initializer directly. Instead, declare a property with the
/// `@UIBindable` attribute, and provide an initial value.
public init(
wrappedValue: Value,
file: StaticString = #file,
fileID: StaticString = #fileID,
line: UInt = #line
) {
self.init(
objectIdentifier: ObjectIdentifier(wrappedValue),
wrappedValue: wrappedValue,
file: file,
fileID: fileID,
line: line
)
}
}
#endif
extension UIBindable: Identifiable where Value: Identifiable {
public var id: Value.ID {
wrappedValue.id
}
}