@@ -11,6 +11,7 @@ flat collection of data, handing it off to SwiftUI, and letting it take care of
1111It also allows for complex and recursive navigation paths in your application.
1212
1313 * [ Basics] ( #Basics )
14+ * [ Pushing features onto the stack] ( #Pushing-features-onto-the-stack )
1415 * [ Integration] ( #Integration )
1516 * [ Dismissal] ( #Dismissal )
1617 * [ Testing] ( #Testing )
@@ -36,35 +37,17 @@ struct RootFeature {
3637 // ...
3738
3839 @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)
6244 }
6345}
6446```
6547
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.
6851
6952Once the ` Path ` reducer is defined we can then hold onto `` StackState `` and `` StackAction `` in the
7053feature that manages the navigation stack:
@@ -78,18 +61,18 @@ struct RootFeature {
7861 // ...
7962 }
8063 enum Action {
81- case path (StackAction <Path.State, Path.Action >)
64+ case path (StackActionOf <Path>)
8265 // ...
8366 }
8467}
8568```
8669
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 ` .
8973
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:
9376
9477``` swift
9578@Reducer
@@ -100,13 +83,14 @@ struct RootFeature {
10083 Reduce { state, action in
10184 // Core logic for root feature
10285 }
103- .forEach (\.path , action : \.path ) {
104- Path ()
105- }
86+ .forEach (\.path , action : \.path )
10687 }
10788}
10889```
10990
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+
11094That completes the steps to integrate the child and parent features together for a navigation stack.
11195
11296Next we must integrate the child and parent views together. This is done by a
@@ -148,14 +132,16 @@ struct RootView: View {
148132The root view can be anything you want, and would typically have some ` NavigationLink ` s or other
149133buttons that push new data onto the `` StackState `` held in your domain.
150134
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:
152138
153139``` swift
154140} 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) :
159145 }
160146}
161147```
@@ -168,19 +154,13 @@ scope the store down to a specific case of the `Path.State` enum:
168154
169155``` swift
170156} 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)
184164 }
185165}
186166```
@@ -191,6 +171,57 @@ additional features to the stack by adding a new case to the `Path` reducer stat
191171and you get complete introspection into what is happening in each child feature from the parent.
192172Continue reading into < doc:StackBasedNavigation#Integration > for more information on that.
193173
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+
194225## Integration
195226
196227Once 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
211242
212243``` swift
213244case 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
215246 else { return .none }
216247
217248 state.path .pop (from : id)
@@ -365,7 +396,7 @@ struct Feature {
365396 var path = StackState< Path.State > ()
366397 }
367398 enum Action {
368- case path (StackAction <Path.State, Path.Action >)
399+ case path (StackActionOf <Path>)
369400 }
370401
371402 @Reducer
0 commit comments