Skip to content

Commit 43e28a5

Browse files
Update docs for next release (#2238)
* Update docs for next release * wip * fix deprecations --------- Co-authored-by: Brandon Williams <[email protected]>
1 parent b2f3ba5 commit 43e28a5

File tree

8 files changed

+193
-14
lines changed

8 files changed

+193
-14
lines changed

Sources/ComposableArchitecture/Documentation.docc/Articles/Bindings.md

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,10 @@ struct Settings: ReducerProtocol {
231231
}
232232
```
233233

234-
Binding actions are constructed and sent to the store by calling
235-
``ViewStore/binding(_:fileID:line:)`` with a key path to the binding state:
234+
Binding actions are constructed and sent to the store by invoking dynamic member lookup on the view:
236235

237236
```swift
238-
TextField("Display name", text: viewStore.binding(\.$displayName))
237+
TextField("Display name", text: viewStore.$displayName)
239238
```
240239

241240
Should you need to layer additional functionality over these bindings, your reducer can pattern
@@ -274,3 +273,103 @@ store.send(.set(\.$protectMyPosts, true)) {
274273
$0.protectMyPosts = true
275274
)
276275
```
276+
277+
> Tip: If you use `@BindingState` on a larger struct and would like to observe changes to smaller
278+
> fields, apply the ``ReducerProtocol/onChange(of:_:)`` modifier to the ``BindingReducer``:
279+
>
280+
> ```swift
281+
> struct Settings: ReducerProtocol {
282+
> struct State {
283+
> @BindingState var developerSettings: DeveloperSettings
284+
> // ...
285+
> }
286+
> // ...
287+
> var body: some ReducerProtocol<State, Action> {
288+
> BindingReducer()
289+
> .onChange(of: \.developerSettings.showDiagnostics) { oldValue, newValue in
290+
> // Logic for when `showDiagnostics` changes...
291+
> }
292+
>
293+
> // ...
294+
> }
295+
> }
296+
> ```
297+
298+
### Binding view state and binding view stores
299+
300+
When a view store observes state bundled up in a "view state" struct (as described in
301+
<doc:Performance#View-stores>), a couple additional tools are required. First, the `ViewState`
302+
struct must annotate the fields it will hold onto with the ``BindingViewState`` property wrapper:
303+
304+
```swift
305+
struct NotificationSettingsView: View {
306+
let store: StoreOf<Settings>
307+
308+
struct ViewState: Equatable {
309+
@BindingViewState var enableNotifications: Bool
310+
@BindingViewState var sendEmailNotifications: Bool
311+
@BindingViewState var sendMobileNotifications: Bool
312+
}
313+
314+
// ...
315+
}
316+
```
317+
318+
And then, when the view store is constructed, we can invoke the
319+
``WithViewStore/init(_:observe:content:file:line:)-4gpoj`` initializer, which is handed a
320+
``BindingViewStore`` that can produce ``BindingViewState`` values from a store:
321+
322+
```swift
323+
struct NotificationSettingsView: View {
324+
// ...
325+
326+
var body: some View {
327+
WithViewStore(
328+
self.store,
329+
observe: { bindingViewStore in
330+
ViewState(
331+
enableNotifications: bindingViewStore.$enableNotifications,
332+
sendEmailNotifications: bindingViewStore.$sendEmailNotifications,
333+
sendMobileNotifications: bindingViewStore.$sendMobileNotifications
334+
)
335+
}
336+
) {
337+
// ...
338+
}
339+
}
340+
}
341+
```
342+
343+
We recommend extracting this work to simplify the call site, _e.g._ with an initializer on your
344+
`ViewState` struct:
345+
346+
```swift
347+
struct NotificationSettingsView: View {
348+
// ...
349+
struct ViewState: Equatable {
350+
// ...
351+
352+
init(bindingViewStore: BindingViewStore<Settings.State>) {
353+
self._enableNotifications = bindingViewStore.$enableNotifications
354+
self._sendEmailNotifications = bindingViewStore.$sendEmailNotifications
355+
self._sendMobileNotifications = bindingViewStore.$sendMobileNotifications
356+
}
357+
}
358+
359+
var body: some View {
360+
WithViewStore(self.store, ViewStore.init) { viewStore in
361+
// ...
362+
}
363+
}
364+
}
365+
```
366+
367+
Finally, you can use dynamic member lookup on the view store to pluck out any view state bindings:
368+
369+
```swift
370+
Form {
371+
Toggle("Enable notifications", isOn: viewStore.$enableNotifications)
372+
373+
// ...
374+
}
375+
```

Sources/ComposableArchitecture/Documentation.docc/ComposableArchitecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ day-to-day when building applications, such as:
6363

6464
### Integrations
6565

66-
- <doc:SwiftUI>
66+
- <doc:SwiftUIIntegration>
6767
- <doc:UIKit>
6868

6969
### Testing

Sources/ComposableArchitecture/Documentation.docc/Extensions/Deprecations/SwiftUIDeprecations.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ Avoid using deprecated APIs in your app. Select a method to see the replacement
1212

1313
- ``ActionSheetState``
1414

15-
### BindableState
15+
### Bindings
1616

1717
- ``BindableState``
18+
- ``ViewStore/binding(_:fileID:line:)``
1819

1920
### ForEachStore
2021

Sources/ComposableArchitecture/Documentation.docc/Extensions/ReducerProtocol.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
- ``dependency(_:_:)``
3535
- ``transformDependency(_:transform:)``
36+
- ``onChange(of:_:)``
3637
- ``signpost(_:log:)``
3738
- ``_printChanges(_:)``
3839

Sources/ComposableArchitecture/Documentation.docc/Extensions/Store.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111

1212
- ``scope(state:action:)-9iai9``
1313

14+
### Accessing state
15+
16+
- ``withState(_:)``
17+
18+
### Sending actions
19+
20+
- ``send(_:)``
21+
1422
### Combine integration
1523

1624
- ``StorePublisher``

Sources/ComposableArchitecture/Documentation.docc/Extensions/SwiftUI.md renamed to Sources/ComposableArchitecture/Documentation.docc/Extensions/SwiftUIIntegration.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Integrating the Composable Architecture into a SwiftUI application.
44

55
## Overview
66

7-
The Composable Architecture can be used to power applications built in many frameworks, but it was designed with SwiftUI in mind, and comes with many powerful tools to integrate into your SwiftUI applications.
7+
The Composable Architecture can be used to power applications built in many frameworks, but it was
8+
designed with SwiftUI in mind, and comes with many powerful tools to integrate into your SwiftUI applications.
89

910
## Topics
1011

@@ -24,7 +25,8 @@ The Composable Architecture can be used to power applications built in many fram
2425
- ``BindableAction``
2526
- ``BindingAction``
2627
- ``BindingReducer``
27-
- ``ViewStore/binding(_:fileID:line:)``
28+
- ``BindingViewState``
29+
- ``BindingViewStore``
2830

2931
<!--DocC: Can't currently document `View` extensions-->
3032
<!--### View Modifiers-->

Sources/ComposableArchitecture/SwiftUI/Alert.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ extension View {
4242
switch button.action.type {
4343
case let .send(action):
4444
if let action = action {
45-
_ = store.send(.presented(fromDestinationAction(action)))
45+
store.send(.presented(fromDestinationAction(action)))
4646
}
4747
case let .animatedSend(action, animation):
4848
if let action = action {
49-
_ = withAnimation(animation) {
49+
withAnimation(animation) {
5050
store.send(.presented(fromDestinationAction(action)))
5151
}
5252
}

Sources/ComposableArchitecture/SwiftUI/Binding.swift

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ extension BindableAction {
151151
}
152152
}
153153

154+
/// A property wrapper type that can designate properties of view state that can be directly
155+
/// bindable in SwiftUI views.
156+
///
157+
/// Read <doc:Bindings> for more information.
154158
@propertyWrapper
155159
public struct BindingViewState<Value> {
156160
let binding: Binding<Value>
@@ -196,6 +200,9 @@ where Value: CustomDebugStringConvertible {
196200
}
197201
}
198202

203+
/// A property wrapper type that can derive ``BindingViewState`` values for a ``ViewStore``.
204+
///
205+
/// Read <doc:Bindings> for more information.
199206
@dynamicMemberLookup
200207
@propertyWrapper
201208
public struct BindingViewStore<State> {
@@ -271,6 +278,19 @@ public struct BindingViewStore<State> {
271278
}
272279

273280
extension WithViewStore where Content: View {
281+
/// Initializes a structure that transforms a ``Store`` into an observable ``ViewStore`` in order
282+
/// to compute bindings and views from state.
283+
///
284+
/// Read <doc:Bindings> for more information.
285+
///
286+
/// - Parameters:
287+
/// - store: A store.
288+
/// - toViewState: A function that transforms binding store state into observable view state.
289+
/// All changes to the view state will cause the `WithViewStore` to re-compute its view.
290+
/// - fromViewAction: A function that transforms view actions into store action.
291+
/// - isDuplicate: A function to determine when two `ViewState` values are equal. When values
292+
/// are equal, repeat view computations are removed,
293+
/// - content: A function that can generate content from a view store.
274294
public init<State, Action>(
275295
_ store: Store<State, Action>,
276296
observe toViewState: @escaping (BindingViewStore<State>) -> ViewState,
@@ -298,6 +318,18 @@ extension WithViewStore where Content: View {
298318
)
299319
}
300320

321+
/// Initializes a structure that transforms a ``Store`` into an observable ``ViewStore`` in order
322+
/// to compute bindings and views from state.
323+
///
324+
/// Read <doc:Bindings> for more information.
325+
///
326+
/// - Parameters:
327+
/// - store: A store.
328+
/// - toViewState: A function that transforms binding store state into observable view state.
329+
/// All changes to the view state will cause the `WithViewStore` to re-compute its view.
330+
/// - isDuplicate: A function to determine when two `ViewState` values are equal. When values
331+
/// are equal, repeat view computations are removed,
332+
/// - content: A function that can generate content from a view store.
301333
public init<State>(
302334
_ store: Store<State, ViewAction>,
303335
observe toViewState: @escaping (BindingViewStore<State>) -> ViewState,
@@ -319,6 +351,17 @@ extension WithViewStore where Content: View {
319351
}
320352

321353
extension WithViewStore where ViewState: Equatable, Content: View {
354+
/// Initializes a structure that transforms a ``Store`` into an observable ``ViewStore`` in order
355+
/// to compute bindings and views from state.
356+
///
357+
/// Read <doc:Bindings> for more information.
358+
///
359+
/// - Parameters:
360+
/// - store: A store.
361+
/// - toViewState: A function that transforms binding store state into observable view state.
362+
/// All changes to the view state will cause the `WithViewStore` to re-compute its view.
363+
/// - fromViewAction: A function that transforms view actions into store action.
364+
/// - content: A function that can generate content from a view store.
322365
public init<State, Action>(
323366
_ store: Store<State, Action>,
324367
observe toViewState: @escaping (BindingViewStore<State>) -> ViewState,
@@ -338,6 +381,16 @@ extension WithViewStore where ViewState: Equatable, Content: View {
338381
)
339382
}
340383

384+
/// Initializes a structure that transforms a ``Store`` into an observable ``ViewStore`` in order
385+
/// to compute bindings and views from state.
386+
///
387+
/// Read <doc:Bindings> for more information.
388+
///
389+
/// - Parameters:
390+
/// - store: A store.
391+
/// - toViewState: A function that transforms binding store state into observable view state.
392+
/// All changes to the view state will cause the `WithViewStore` to re-compute its view.
393+
/// - content: A function that can generate content from a view store.
341394
public init<State>(
342395
_ store: Store<State, ViewAction>,
343396
observe toViewState: @escaping (BindingViewStore<State>) -> ViewState,
@@ -388,11 +441,26 @@ extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewSt
388441
)
389442
}
390443

391-
// TODO: Deprecate?
392-
/// Returns a binding to the resulting binding state of a given key path.
393-
///
394-
/// - Parameter keyPath: A key path to a specific binding state.
395-
/// - Returns: A new binding.
444+
@available(
445+
iOS,
446+
deprecated: 9999,
447+
message: "Use 'viewStore.$value' instead."
448+
)
449+
@available(
450+
macOS,
451+
deprecated: 9999,
452+
message: "Use 'viewStore.$value' instead."
453+
)
454+
@available(
455+
tvOS,
456+
deprecated: 9999,
457+
message: "Use 'viewStore.$value' instead."
458+
)
459+
@available(
460+
watchOS,
461+
deprecated: 9999,
462+
message: "Use 'viewStore.$value' instead."
463+
)
396464
public func binding<Value: Equatable>(
397465
_ keyPath: WritableKeyPath<ViewState, BindingState<Value>>,
398466
fileID: StaticString = #fileID,

0 commit comments

Comments
 (0)