I'm trying to get a deep nested programmatic navigation stack in order. The following code works as expected when navigation is done by hand (ie: pressing the links). When you press the Set Nav
button the navigation stack does change - but not as expected - and you end up with a broken stack [start -> b -> bbb]
with much flipping between views
class NavState: ObservableObject {
@Published var firstLevel: String? = nil
@Published var secondLevel: String? = nil
@Published var thirdLevel: String? = nil
}
struct LandingPageView: View {
@ObservedObject var navigationState: NavState
func resetNav() {
self.navigationState.firstLevel = "b"
self.navigationState.secondLevel = "ba"
self.navigationState.thirdLevel = "bbb"
}
var body: some View {
return NavigationView {
List {
NavigationLink(
destination: Place(
text: "a",
childValues: [ ("aa", [ "aaa"]) ],
navigationState: self.navigationState
).navigationBarTitle("a"),
tag: "a",
selection: self.$navigationState.firstLevel
) {
Text("a")
}
NavigationLink(
destination: Place(
text: "b",
childValues: [ ("bb", [ "bbb"]), ("ba", [ "baa", "bbb" ]) ],
navigationState: self.navigationState
).navigationBarTitle("b"),
tag: "b",
selection: self.$navigationState.firstLevel
) {
Text("b")
}
Button(action: self.resetNav) {
Text("Set Nav")
}
}
.navigationBarTitle("Start")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct Place: View {
var text: String
var childValues: [ (String, [String]) ]
@ObservedObject var navigationState: NavState
var body: some View {
List(childValues, id: \.self.0) { childValue in
NavigationLink(
destination: NextPlace(
text: childValue.0,
childValues: childValue.1,
navigationState: self.navigationState
).navigationBarTitle(childValue.0),
tag: childValue.0,
selection: self.$navigationState.secondLevel
) {
Text(childValue.0)
}
}
}
}
struct NextPlace: View {
var text: String
var childValues: [String]
@ObservedObject var navigationState: NavState
var body: some View {
List(childValues, id: \.self) { childValue in
NavigationLink(
destination: FinalPlace(
text: childValue,
navigationState: self.navigationState
).navigationBarTitle(childValue),
tag: childValue,
selection: self.$navigationState.thirdLevel
) {
Text(childValue)
}
}
}
}
struct FinalPlace: View {
var text: String
@ObservedObject var navigationState: NavState
var body: some View {
let concat: String = "\(navigationState.firstLevel)/\(navigationState.secondLevel))/\(navigationState.thirdLevel)/"
return VStack {
Text(text)
Text(concat)
}
}
}
I originally attempted to tackle navigation transition animations as a problem source - but How to disable NavigationView push and pop animations is suggesting that this is not configurable
Are there any sane examples of >1 level programmatic navigation working out there?
Edit: Part of what I am looking to get here is also initial state for navigation working correctly - if I come in from an external context with a navigation state I wish to reflect (ie: from a notification with some in-app context to start from, or from a saved-to-disk-encoded-state) then I would expect to be able to load up the top View
with navigation correctly pointing to the right child view. Essentially - replace the nil
s in the NavState
with real values. Qt's QML and ReactRouter can both do this declaratively - SwiftUI should be able to as well.
Update: Xcode 14 / SwiftUI4
Now we have
NavigationStack
with dynamic heterogenous path support. So updated approach could be as follows.Note: although original could be simplified now a lot: a) I wanted preserve view hierarchy b) I wanted to show handling of different model types
Tested with Xcode 14 / iOS 16
Test on GitHub
Original
This is because new stack level is formed when animation is completed, and that's why it works in the case of manual tap.
With the following modification it works: