Skip to content

Commit c3842e5

Browse files
ObservedViewStore for projectedBindings (#770)
* Added observedSelf property to ViewStore * Created ObservedViewStore * Try projectedBinding first for ViewStore bindings * Replaced @ObservedObject with @ObservedViewStore * Revert "Replaced @ObservedObject with @ObservedViewStore" This reverts commit 6aadf373db58a2320c582741a76794ac3d6bfc43. * Revert "Try projectedBinding first for ViewStore bindings" This reverts commit 0e2fae1cbf53e84194a6fb3a5eb077021110c395. * Revert "Created ObservedViewStore" This reverts commit 7f99a718b96d186324627ef6d016a882e83e9f8e. * Revert "Added observedSelf property to ViewStore" This reverts commit 65dc3837b37d8251c2d1e2916c94894b8eb93db0. * Replaced self.binding with projectedValue * Revert "Replaced self.binding with projectedValue" This reverts commit cc2b69777e63a8b8cee40df5d5db9557302177cd. * Derive bindings using WrappedState helper * wip * wip * wip * avoid potential retain cycle * Revert "Derive bindings using WrappedState helper" This reverts commit f3fb18f4458f47d28905e8b346c767fbd05b7f99. Co-authored-by: Stephen Celis <[email protected]>
1 parent 6133d3e commit c3842e5

File tree

1 file changed

+51
-48
lines changed

1 file changed

+51
-48
lines changed

Sources/ComposableArchitecture/ViewStore.swift

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -151,54 +151,43 @@ public final class ViewStore<State, Action> {
151151
self._send(action)
152152
}
153153

154-
#if canImport(SwiftUI)
155-
/// Derives a binding from the store that prevents direct writes to state and instead sends
156-
/// actions to the store.
157-
///
158-
/// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s
159-
/// since the ``Store`` does not allow directly writing its state; it only allows reading state
160-
/// and sending actions.
161-
///
162-
/// For example, a text field binding can be created like this:
163-
///
164-
/// ```swift
165-
/// struct State { var name = "" }
166-
/// enum Action { case nameChanged(String) }
167-
///
168-
/// TextField(
169-
/// "Enter name",
170-
/// text: viewStore.binding(
171-
/// get: { $0.name },
172-
/// send: { Action.nameChanged($0) }
173-
/// )
174-
/// )
175-
/// ```
176-
///
177-
/// - Parameters:
178-
/// - get: A function to get the state for the binding from the view
179-
/// store's full state.
180-
/// - localStateToViewAction: A function that transforms the binding's value
181-
/// into an action that can be sent to the store.
182-
/// - Returns: A binding.
183-
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
184-
public func binding<LocalState>(
185-
get: @escaping (State) -> LocalState,
186-
send localStateToViewAction: @escaping (LocalState) -> Action
187-
) -> Binding<LocalState> {
188-
Binding(
189-
get: { get(self._state) },
190-
set: { newLocalState, transaction in
191-
if transaction.animation != nil {
192-
withTransaction(transaction) {
193-
self.send(localStateToViewAction(newLocalState))
194-
}
195-
} else {
196-
self.send(localStateToViewAction(newLocalState))
197-
}
198-
}
199-
)
200-
}
201-
154+
#if canImport(SwiftUI)
155+
/// Derives a binding from the store that prevents direct writes to state and instead sends
156+
/// actions to the store.
157+
///
158+
/// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s
159+
/// since the ``Store`` does not allow directly writing its state; it only allows reading state
160+
/// and sending actions.
161+
///
162+
/// For example, a text field binding can be created like this:
163+
///
164+
/// ```swift
165+
/// struct State { var name = "" }
166+
/// enum Action { case nameChanged(String) }
167+
///
168+
/// TextField(
169+
/// "Enter name",
170+
/// text: viewStore.binding(
171+
/// get: { $0.name },
172+
/// send: { Action.nameChanged($0) }
173+
/// )
174+
/// )
175+
/// ```
176+
///
177+
/// - Parameters:
178+
/// - get: A function to get the state for the binding from the view
179+
/// store's full state.
180+
/// - localStateToViewAction: A function that transforms the binding's value
181+
/// into an action that can be sent to the store.
182+
/// - Returns: A binding.
183+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
184+
public func binding<LocalState>(
185+
get: @escaping (State) -> LocalState,
186+
send localStateToViewAction: @escaping (LocalState) -> Action
187+
) -> Binding<LocalState> {
188+
ObservedObject(wrappedValue: self)
189+
.projectedValue[get: .init(rawValue: get), send: .init(rawValue: localStateToViewAction)]
190+
}
202191
/// Derives a binding from the store that prevents direct writes to state and instead sends
203192
/// actions to the store.
204193
///
@@ -296,6 +285,14 @@ public final class ViewStore<State, Action> {
296285
deinit {
297286
viewDisposable?.dispose()
298287
}
288+
289+
private subscript<LocalState>(
290+
get state: HashableWrapper<(State) -> LocalState>,
291+
send action: HashableWrapper<(LocalState) -> Action>
292+
) -> LocalState {
293+
get { state.rawValue(self.state) }
294+
set { self.send(action.rawValue(newValue)) }
295+
}
299296
}
300297

301298
extension ViewStore where State: Equatable {
@@ -353,3 +350,9 @@ public struct StoreProducer<State>: SignalProducerConvertible {
353350
self.upstream.map(keyPath).skipRepeats()
354351
}
355352
}
353+
354+
private struct HashableWrapper<Value>: Hashable {
355+
let rawValue: Value
356+
static func == (lhs: Self, rhs: Self) -> Bool { false }
357+
func hash(into hasher: inout Hasher) {}
358+
}

0 commit comments

Comments
 (0)