I am trying to integrate NavigationStack
in my SwiftUI app.
I have four views: CealUIApp
, OnBoardingView
, UserTypeView
and RegisterView
.
I want to navigate from OnBoardingView
to UserTypeView
when user presses a button in OnBoardingView
.
And, navigate from UserTypeView
to RegisterView
when user presses a button in UserTypeView
Below is my code for CealUIApp
@main
struct CealUIApp: App {
@State private var path = [String]()
var body: some Scene {
WindowGroup {
NavigationStack(path: $path){
OnBoardingView(path: $path)
}
}
}
}
In OnBoardingView
Button {
path.append("UserTypeView")
}
label: {
Text("Hello")
}
.navigationDestination(for: String.self) { string in
UserTypeView(path: $path)
}
In UserTypeView
Button {
path.append("RegisterView")
}
label: {
Text("Hello")
}
.navigationDestination(for: String.self) { string in
RegisterView()
}
When the button on UserTypeView
is pressed, it navigates to UserTypeView
instead of RegisterView
.
Also, the Xcode logs saying Only root-level navigation destinations are effective for a navigation stack with a homogeneous path.
3
Answers
You can get rid of
Only root-level navigation destinations are effective for a navigation stack with a homogeneous path
by changing the path type toNavigationPath
.But then you get a message/error that I think explains the issue better
A navigationDestination for “Swift.String” was declared earlier on the stack. Only the destination declared closest to the root view of the stack will be used.
Apple has decided that scanning all views that are available is very inefficient so they will use the
navigationDestination
will take priority.Just imagine if your
OnBoardingView
also had an option for"RegisterView"
How would SwiftUI pick the right one?
So how to "fix"? You can try this alternative.
Following @lorem ipsum example I think you can change this state variable
@State private var path: NavigationPath = .init()
with an@ObservableObject
so you don’t need to pass@Bindings
on all the views. You just pass it down from theCealUIApp
view as anWnvironmentObject
And then the rest remain the same as @lorem ipsum said.
As an alternative to @lorem ipsum’s answer, I’d suggest using
NavigationLink
instead of aButton
as that will handle adding the values internalNavigationPath
forNavigationStack
. I would only add and pass around your own path if you wanted to do navigation programatically (for example after a network request).First we have an enum to handle the possible routes and creation of their views:
Then we have the main app:
And finally the implementation of those views with the various
NavigationLink
in place: