@@ -11,6 +11,7 @@ flat collection of data, handing it off to SwiftUI, and letting it take care of
11
11
It also allows for complex and recursive navigation paths in your application.
12
12
13
13
* [ Basics] ( #Basics )
14
+ * [ Pushing features onto the stack] ( #Pushing-features-onto-the-stack )
14
15
* [ Integration] ( #Integration )
15
16
* [ Dismissal] ( #Dismissal )
16
17
* [ Testing] ( #Testing )
@@ -36,35 +37,17 @@ struct RootFeature {
36
37
// ...
37
38
38
39
@Reducer
39
- struct Path {
40
- @ObservableState
41
- enum State {
42
- case addItem (AddFeature.State)
43
- case detailItem (DetailFeature.State)
44
- case editItem (EditFeature.State)
45
- }
46
- enum Action {
47
- case addItem (AddFeature.Action)
48
- case detailItem (DetailFeature.Action)
49
- case editItem (EditFeature.Action)
50
- }
51
- var body: some ReducerOf<Self > {
52
- Scope (state : \.addItem , action : \.addItem ) {
53
- AddFeature ()
54
- }
55
- Scope (state : \.editItem , action : \.editItem ) {
56
- EditFeature ()
57
- }
58
- Scope (state : \.detailItem , action : \.detailItem ) {
59
- DetailFeature ()
60
- }
61
- }
40
+ enum Path {
41
+ case addItem (AddFeature)
42
+ case detailItem (DetailFeature)
43
+ case editItem (EditFeature)
62
44
}
63
45
}
64
46
```
65
47
66
- > Note: The ` Path ` reducer is identical to the ` Destination ` reducer that one creates for tree-based
67
- > navigation when using enums. See < doc:TreeBasedNavigation#Enum-state > for more information.
48
+ > Note: The ` Path ` reducer is identical to the ` Destination ` reducer that one creates for
49
+ > tree-based navigation when using enums. See < doc:TreeBasedNavigation#Enum-state > for more
50
+ > information.
68
51
69
52
Once the ` Path ` reducer is defined we can then hold onto `` StackState `` and `` StackAction `` in the
70
53
feature that manages the navigation stack:
@@ -78,18 +61,18 @@ struct RootFeature {
78
61
// ...
79
62
}
80
63
enum Action {
81
- case path (StackAction <Path.State, Path.Action >)
64
+ case path (StackActionOf <Path>)
82
65
// ...
83
66
}
84
67
}
85
68
```
86
69
87
- > Note: `` StackAction `` is generic over both state and action of the ` Path ` domain. This is
88
- > different from `` PresentationAction `` , which only has a single generic.
70
+ > Tip: `` StackAction `` is generic over both state and action of the ` Path ` domain, and so you can
71
+ > use the `` StackActionOf `` typealias to simplify the syntax a bit. This is different from
72
+ > `` PresentationAction `` , which only has a single generic of ` Action ` .
89
73
90
- And then we must make use of the `` Reducer/forEach(_:action:destination:fileID:line:)-yz3v ``
91
- method to integrate the domains of all the features that can be navigated to with the domain of the
92
- parent feature:
74
+ And then we must make use of the `` Reducer/forEach(_:action:) `` method to integrate the domains of
75
+ all the features that can be navigated to with the domain of the parent feature:
93
76
94
77
``` swift
95
78
@Reducer
@@ -100,13 +83,14 @@ struct RootFeature {
100
83
Reduce { state, action in
101
84
// Core logic for root feature
102
85
}
103
- .forEach (\.path , action : \.path ) {
104
- Path ()
105
- }
86
+ .forEach (\.path , action : \.path )
106
87
}
107
88
}
108
89
```
109
90
91
+ > Note: You do not need to specify ` Path() ` in a trailing closure of ` forEach ` because it can be
92
+ > automatically inferred from ` @Reducer enum Path ` .
93
+
110
94
That completes the steps to integrate the child and parent features together for a navigation stack.
111
95
112
96
Next we must integrate the child and parent views together. This is done by a
@@ -148,14 +132,16 @@ struct RootView: View {
148
132
The root view can be anything you want, and would typically have some ` NavigationLink ` s or other
149
133
buttons that push new data onto the `` StackState `` held in your domain.
150
134
151
- And the last trailing closure is provided a store of ` Path ` domain so that you can switch on it:
135
+ And the last trailing closure is provided a store of ` Path ` domain, and you can use the
136
+ `` Store/case `` computed property to destructure each case of the ` Path ` to obtain a store focused
137
+ on just that case:
152
138
153
139
``` swift
154
140
} destination : { store in
155
- switch store.state {
156
- case .addItem :
157
- case .detailItem :
158
- case .editItem :
141
+ switch store.case {
142
+ case .addItem ( let store) :
143
+ case .detailItem ( let store) :
144
+ case .editItem ( let store) :
159
145
}
160
146
}
161
147
```
@@ -168,19 +154,13 @@ scope the store down to a specific case of the `Path.State` enum:
168
154
169
155
``` swift
170
156
} destination : { store in
171
- switch store.state {
172
- case .addItem :
173
- if let store = store.scope (state : \.addItem , action : \.addItem ) {
174
- AddView (store : store)
175
- }
176
- case .detailItem :
177
- if let store = store.scope (state : \.detailItem , action : \.detailItem ) {
178
- DetailView (store : store)
179
- }
180
- case .editItem :
181
- if let store = store.scope (state : \.editItem , action : \.editItem ) {
182
- EditView (store : store)
183
- }
157
+ switch store.case {
158
+ case .addItem (let store):
159
+ AddView (store : store)
160
+ case .detailItem (let store):
161
+ DetailView (store : store)
162
+ case .editItem (let store):
163
+ EditView (store : store)
184
164
}
185
165
}
186
166
```
@@ -191,6 +171,57 @@ additional features to the stack by adding a new case to the `Path` reducer stat
191
171
and you get complete introspection into what is happening in each child feature from the parent.
192
172
Continue reading into < doc:StackBasedNavigation#Integration > for more information on that.
193
173
174
+ ## Pushing features onto the stack
175
+
176
+ There are two primary ways to push features onto the stack once you have their domains integrated
177
+ and ` NavigationStack ` in the view, as described above. The simplest way is to use the
178
+ `` SwiftUI/NavigationLink/init(state:label:fileID:line:) `` initializer on ` NavigationLink ` , which
179
+ requires you to specify the state of the feature you want to push onto the stack. You must specify
180
+ the full state, going all the way back to the ` Path ` reducer's state:
181
+
182
+ ``` swift
183
+ Form {
184
+ NavigationLink (
185
+ state : RootFeature.Path .State .detail (DetailFeature.State ())
186
+ ) {
187
+ Text (" Detail" )
188
+ }
189
+ }
190
+ ```
191
+
192
+ When the link is tapped a `` StackAction/push(id:state:) `` action will be sent, causing the ` path `
193
+ collection to be mutated and appending the ` .detail ` state to the stack.
194
+
195
+ This is by far the simplest way to navigate to a screen, but it also has its drawbacks. In
196
+ particular, it makes modularity difficult since the view that holds onto the ` NavigationLink ` must
197
+ have access to the ` Path.State ` type, which means it needs to build all of the ` Path ` reducer,
198
+ including _ every_ feature that can be navigated to.
199
+
200
+ This hurts modularity because it is no longer possible to build each feature that can be presented
201
+ in the stack individually, in full isolation. You must build them all together. Technically you can
202
+ move all features' ` State ` types (and only the ` State ` types) to a separate module, and then
203
+ features can depend on only that module without needing to build every feature's reducer.
204
+
205
+ Another alternative is to forgo ` NavigationLink ` entirely and just use ` Button ` that sends an action
206
+ in the child feature's domain:
207
+
208
+ ``` swift
209
+ Form {
210
+ Button (" Detail" ) {
211
+ store.send (.detailButtonTapped )
212
+ }
213
+ }
214
+ ```
215
+
216
+ Then the root feature can listen for that action and append to the ` path ` with new state in order
217
+ to drive navigation:
218
+
219
+ ``` swift
220
+ case .path (.element (id : _ , action : .list (.detailButtonTapped ))):
221
+ state.path .append (.detail (DetailFeature.State ()))
222
+ return .none
223
+ ```
224
+
194
225
## Integration
195
226
196
227
Once your features are integrated together using the steps above, your parent feature gets instant
@@ -211,7 +242,7 @@ additional logic, such as popping the "edit" feature and saving the edited item
211
242
212
243
``` swift
213
244
case let .path (.element (id : id, action : .editItem (.saveButtonTapped ))):
214
- guard case let . editItem ( editItemState) = state.path[id : id]
245
+ guard let editItemState = state.path[id : id]? .editItem
215
246
else { return .none }
216
247
217
248
state.path .pop (from : id)
@@ -365,7 +396,7 @@ struct Feature {
365
396
var path = StackState< Path.State > ()
366
397
}
367
398
enum Action {
368
- case path (StackAction <Path.State, Path.Action >)
399
+ case path (StackActionOf <Path>)
369
400
}
370
401
371
402
@Reducer
0 commit comments