Skip to content

Commit d0ec3ea

Browse files
committed
Add Bindable implementation
1 parent cef2329 commit d0ec3ea

File tree

2 files changed

+174
-2
lines changed

2 files changed

+174
-2
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//
2+
// Bindable.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
public import OpenObservation
9+
#if OPENSWIFTUI_OPENCOMBINE
10+
public import OpenCombine
11+
#else
12+
public import Combine
13+
#endif
14+
15+
/// A property wrapper type that supports creating bindings to the mutable
16+
/// properties of observable objects.
17+
///
18+
/// Use this property wrapper to create bindings to mutable properties of a
19+
/// data model object that conforms to the
20+
/// [Observable](https://swiftpackageindex.com/openswiftuiproject/openobservation/main/documentation/openobservation/observable)
21+
/// protocol. For example, the following code wraps the `book` input with
22+
/// `@Bindable`. Then it uses a ``TextField`` to change the `title` property of
23+
/// a book, and a ``Toggle`` to change the `isAvailable` property, using the
24+
/// `$` syntax to pass a binding for each property to those controls.
25+
///
26+
/// @Observable
27+
/// class Book: Identifiable {
28+
/// var title = "Sample Book Title"
29+
/// var isAvailable = true
30+
/// }
31+
///
32+
/// struct BookEditView: View {
33+
/// @Bindable var book: Book
34+
/// @Environment(\.dismiss) private var dismiss
35+
///
36+
/// var body: some View {
37+
/// Form {
38+
/// TextField("Title", text: $book.title)
39+
///
40+
/// Toggle("Book is available", isOn: $book.isAvailable)
41+
///
42+
/// Button("Close") {
43+
/// dismiss()
44+
/// }
45+
/// }
46+
/// }
47+
/// }
48+
///
49+
/// You can use the `Bindable` property wrapper on properties and variables to
50+
/// an [Observable](https://swiftpackageindex.com/openswiftuiproject/openobservation/main/documentation/openobservation/observable)
51+
/// object. This includes global variables, properties that exists outside of
52+
/// OpenSwiftUI types, or even local variables. For example, you can create a
53+
/// `@Bindable` variable within a view's ``View/body-swift.property``:
54+
///
55+
/// struct LibraryView: View {
56+
/// @State private var books = [Book(), Book(), Book()]
57+
///
58+
/// var body: some View {
59+
/// List(books) { book in
60+
/// @Bindable var book = book
61+
/// TextField("Title", text: $book.title)
62+
/// }
63+
/// }
64+
/// }
65+
///
66+
/// The `@Bindable` variable `book` provides a binding that connects
67+
/// ``TextField`` to the `title` property of a book so that a person can make
68+
/// changes directly to the model data.
69+
///
70+
/// Use this same approach when you need a binding to a property of an
71+
/// observable object stored in a view's environment. For example, the
72+
/// following code uses the ``Environment`` property wrapper to retrieve an
73+
/// instance of the observable type `Book`. Then the code creates a `@Bindable`
74+
/// variable `book` and passes a binding for the `title` property to a
75+
/// ``TextField`` using the `$` syntax.
76+
///
77+
/// struct TitleEditView: View {
78+
/// @Environment(Book.self) private var book
79+
///
80+
/// var body: some View {
81+
/// @Bindable var book = book
82+
/// TextField("Title", text: $book.title)
83+
/// }
84+
/// }
85+
///
86+
@available(OpenSwiftUI_v4_0, *)
87+
@dynamicMemberLookup
88+
@propertyWrapper
89+
public struct Bindable<Value> {
90+
91+
/// The wrapped object.
92+
public var wrappedValue: Value
93+
94+
/// The bindable wrapper for the object that creates bindings to its
95+
/// properties using dynamic member lookup.
96+
public var projectedValue: Bindable<Value> { self }
97+
98+
@available(*, unavailable, message: "The wrapped value must be an object that conforms to Observable")
99+
public init(wrappedValue: Value) {
100+
self.wrappedValue = wrappedValue
101+
}
102+
103+
@available(*, unavailable, message: "The wrapped value must be an object that conforms to Observable")
104+
public init(projectedValue: Bindable<Value>) {
105+
self.wrappedValue = projectedValue.wrappedValue
106+
}
107+
}
108+
109+
@available(OpenSwiftUI_v4_0, *)
110+
extension Bindable where Value: AnyObject {
111+
112+
/// Returns a binding to the value of a given key path.
113+
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<Value, Subject>) -> Binding<Subject> {
114+
Binding(wrappedValue, keyPath: keyPath)
115+
}
116+
}
117+
118+
extension Bindable where Value: ObservableObject {
119+
120+
@available(*, unavailable, message: "@Bindable only works with Observable types. For ObservableObject types, use @ObservedObject instead.")
121+
public init(wrappedValue: Value) {
122+
self.wrappedValue = wrappedValue
123+
}
124+
}
125+
126+
@available(OpenSwiftUI_v4_0, *)
127+
extension Bindable where Value: AnyObject, Value: Observable {
128+
129+
/// Creates a bindable object from an observable object.
130+
///
131+
/// You should not call this initializer directly. Instead, declare a
132+
/// property with the `@Bindable` attribute, and provide an initial value.
133+
public init(wrappedValue: Value) {
134+
self.wrappedValue = wrappedValue
135+
}
136+
137+
/// Creates a bindable object from an observable object.
138+
///
139+
/// This initializer is equivalent to ``init(wrappedValue:)``, but is more
140+
/// succinct when when creating bindable objects nested within other
141+
/// expressions. For example, you can use the initializer to create a
142+
/// bindable object inline with code that declares a view that takes a
143+
/// binding as a parameter:
144+
///
145+
/// struct TitleEditView: View {
146+
/// @Environment(Book.self) private var book
147+
///
148+
/// var body: some View {
149+
/// TextField("Title", text: Bindable(book).title)
150+
/// }
151+
/// }
152+
///
153+
public init(_ wrappedValue: Value) {
154+
self.wrappedValue = wrappedValue
155+
}
156+
157+
/// Creates a bindable from the value of another bindable.
158+
public init(projectedValue: Bindable<Value>) {
159+
self.wrappedValue = projectedValue.wrappedValue
160+
}
161+
}
162+
163+
@available(OpenSwiftUI_v4_0, *)
164+
extension Bindable: Identifiable where Value: Identifiable {
165+
public var id: Value.ID {
166+
wrappedValue.id
167+
}
168+
}
169+
170+
@available(OpenSwiftUI_v4_0, *)
171+
extension Bindable: Sendable where Value: Sendable {}

Sources/OpenSwiftUICore/Data/Binding/Binding.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//
22
// Binding.swift
3-
// OpenSwiftUI
3+
// OpenSwiftUICore
44
//
55
// Audited for 3.5.2
66
// Status: Complete
7-
// ID: 5436F2B399369BE3B016147A5F8FE9F2
7+
// ID: 5436F2B399369BE3B016147A5F8FE9F2 (SwiftUI)
8+
// ID: C453EE81E759852CCC6400C47D93A43E (SwiftUICore)
89

910
/// A property wrapper type that can read and write a value owned by a source of
1011
/// truth.

0 commit comments

Comments
 (0)