You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR adds new scoping methods to `Store` that allow you to create
child stores from nested observable state.
These same scoping methods already exist for child _models_, typically
created by rendering child workflows and composing their renderings, but
not for nested state within a single workflow.
I've added a new `ObservableCounter` sample to demonstrate. The existing
`ObservableScreen` sample has been renamed to `ObservableComposition`.
## Example
Consider a state like this:
```swift
@ObservableState
struct State {
var children: [ChildState]
@ObservableState
struct ChildState: Identifiable {
let id = UUID()
var foo: Int
}
}
```
If one was trying to iterate over `children` and form bindings to the
`foo` property, you might attempt this construction, which creates a
`Binding<[ChildState]>` and then uses `ForEach` to map over individual
`Binding<ChildState>`s.
```swift
struct ExampleView: View {
@Perception.Bindable
var store: Store<StateAccessor<State>>
var body: some View {
ForEach($store.children) { child in
FooView(foo: child.foo)
}
}
}
```
Unfortunately this will compile and run, but writes to `child.foo` will
not cause `FooView` to be invalidated. The exact mechanics behind this
are opaque, but the bindings captured by the `ForEach` don't register
observation at the right scope.
Using the new scoping methods, you can iterate over child stores, and
create a binding from each store instead.
```swift
struct ExampleView: View {
var store: Store<StateAccessor<State>>
var body: some View {
ForEach(store.scope(collection: \.children)) { child in
// you can also inline this incantation to the capture list if you prefer
@Perception.Bindable var child = child
FooView(foo: $child.foo)
}
}
}
```
Copy file name to clipboardExpand all lines: Documentation/WorkflowSwiftUI Adoption Guide.md
+77
Original file line number
Diff line number
Diff line change
@@ -219,6 +219,8 @@ struct PersonView: View {
219
219
}
220
220
```
221
221
222
+
`WithPerceptionTracking` is necessary in any view that accesses a `Store`, as well as inside many containers that lazily evaluate their content, including `ForEach`, `GeometryReader`, `List`, `LazyVStack`, and `LazyHStack`.
223
+
222
224
If you forget to wrap your view's body in `WithPerceptionTracking`, a runtime warning will remind you.
223
225
224
226
## Screens
@@ -341,6 +343,81 @@ struct CoupleView: View {
341
343
342
344
Child store mapping works for plain `ObservableModel` properties, as well as optionals, collections, and identified collections.
343
345
346
+
## Nested observable state
347
+
348
+
If your state has properties with types that also conform to `ObservableState`, you can create a child `Store` for those properties, similarly to creating child stores of child models from child workflows. Use the `scope(keyPath:)` methods to create a store for regular properties and optionals, and the `scope(collection:)` methods to create stores for items in collections.
349
+
350
+
Scoping nested state to a child store is not required for simple properties, but is required to create an optional binding, and for collections in most cases. See the example below.
351
+
352
+
```swift
353
+
@ObservableState
354
+
structState {
355
+
356
+
@ObservableState
357
+
structCounterState: Identifiable {
358
+
let id =UUID()
359
+
var count =0
360
+
}
361
+
362
+
var counter =CounterState()
363
+
var optionalCounter: CounterState?=CounterState()
364
+
var moreCounters: [CounterState] = [.init(), .init()]
365
+
var evenMoreCounters: IdentifiedArrayOf<CounterState> = [.init(), .init()]
This demo shows how to compose views using child workflows. `CounterWorkflow` renders a model to be used in `CounterView`. `MultiCounterWorkflow` renders multiple child `CounterWorkflow`s, and `MultiCounterView` composes multiple child `CounterView`s.
This demo shows a single workflow with observable state, which contains an array of nested observable states. In `CounterListView`, the nested states are scoped into bindable stores. The `SimpleCounterView` takes a vanilla binding to an `Int` and has no knowledge of WorkflowSwiftUI.
0 commit comments