Reputation: 1342
I've looked around but haven't seen any info on this. If I want to have multiple navigation stacks in SwiftUI, and have multiple navigation links that add to different navigation stacks, how can I do this? In my use case I use a tab view, so I would be something like:
NavigationStack(path: $globalNavigationStack) {
TabView {
PrimaryView()
SecondaryView()
}
}
Then maybe in PrimaryView:
struct PrimaryView: View {
var body: some View {
NavigationStack(path: $localNavigationStack) {
NavigationLink(value: SomeType.self) {
Button("This should add to local stack")
}
.navigationDestination(for: SomeType.val) { someType in
ViewOnLocalStack()
}
NavigationLink(value: SomeType.self) {
Button("This should add to global stack")
}
.navigationDestination(for: SomeType.val) { someType in
ViewOnGlobalStack()
}
}
}
}
But the second NavigationLink
will add to the local stack. Assuming I have access to the global path, how can I make the second one add to global path? What I tried was this:
NavigationStack(path: $globalNavigationStack) {
TabView {
PrimaryView()
SecondaryView()
}
}
.navigationDestination(for: SomeType.val) { someType in
ViewOnGlobalStack()
}
along with removing the onDestination
from the second link, but the console complained that the navigation link had no corresponding onDestination
. Not sure if I've seen anyone else have a similar situation.
Upvotes: 11
Views: 5500
Reputation: 20096
Here is the basic structure you can use to utilize multiple NavigationStacks.
TabView {
NavigationStack {
VegetableListScreen(vegetables: vegetables)
}.tabItem {
Image(systemName: "leaf")
Text("Vegetables")
}
NavigationStack {
MyGardenScreen()
}.tabItem {
Image(systemName: "house")
Text("My Garden")
}
.badge(myGardenVegetables.count)
NavigationStack {
PestListScreen(pests: pests)
}.tabItem {
Image(systemName: "ladybug")
Text("Pests")
}
}
Upvotes: 0
Reputation: 1382
You can have nested NavigationStack's, and it will work most of the time.
However subtle bugs will creep up, especially if you are using a mix of NavigationLink(destination:) and NavigationLink(value:).
Note: If you bind your NavigationStack to a path NavigationStack(path: $somePath)
, you can open links using either
NavigationLink(destination:)
or NavigationLink(value:)
. Be careful doing this, though:
NavigationLink(destination:)
links are handled ephemerally. So tapping one does push a new view to the screen... but it doesn’t add any data to the path array.NavigationLink(destination:)
or NavigationLink(value:)
For a good mid-level tutorial on a simple use of this stuff, take a look at https://swiftwithmajid.com/2022/10/05/mastering-navigationstack-in-swiftui-navigationpath/.
Upvotes: 0
Reputation: 2735
TLDR; Nested NavigationStack
isn't possible. You'll need a mixed approach. The following answer is advice on how to approach it assuming nesting is not possible.
XCode will not necessarily complain if your try. But even if you're able to make it work your app will quickly crash.
My advice: don't try to hack it. NavigationStack
is decent at managing navigation state at one level deep. To create nuance in your view states, you will either have to create numerous views with isolated state or fewer robust views with shared state.
Here is the bad approach versus some suggested approaches in tree structure. Note that I am using the term "screen" because view has a specific meaning in SwiftUI.
How you're currently imagining your app navigation scheme:
App <- NavigationStack
│
├── Screen1 <- NavigationStack
│ ├── Subscreen1A
│ └── Subscreen1B
│
└── Screen2 <- NavigationStack
├── Subscreen2A
└── Subscreen2B
App <- NavigationStack and global state object for subviews.
│
├── Screen1 <- reacts to global state object for changes
├── Screen2 <- reacts to global state object for changes
App <- NavigationStack
│
├── Screen1 <- custom state object with conditionally rendered subviews
├── Screen2 <- custom state object with conditionally rendered subviews
So I'll note a few points of guidance for how to approach subviews:
NavigationStack
, use these data types to determine how to render the entire view or parts of your view.Lastly, here's an example to illustrate the "better approach" I mentioned earlier. It'll get you started assuming a global state works for now.
import SwiftUI
enum AppRoute: Hashable {
case screenOne
case screenTwo
case screenThree
}
final class AppState: ObservableObject {
@Published var path = NavigationPath()
// Can't be optional.
// Manage in parent to inject state into "child" views
@Published var screenTwoData = ?
@Published var isScreenTwoFlowActive: Bool = false
}
@main
struct MyApp: App {
@StateObject private var appState = AppState()
var body: some Scene {
WindowGroup {
NavigationStack(path: $appState.path) {
VStack {
ScreenOne()
}
.navigationDestination(for: AppRoute.self) { appRoute in
switch appRoute {
case .screenOne:
ScreenTwo()
case .screenTwo:
ScreenThree()
}
}
}
.environmentObject(appState)
}
}
}
Upvotes: 3