I am creating an onboarding flow for an iOS app with the UI written in SwiftUI
SwiftUI
has a NavigationView
UI component that uses NavigationLinks
to enable a user to move to the next view
However, most examples on the internet only provide for one level of view depth. This means that only one view from the root view is put on the navigation stack.
In the onboarding flow a user will move to the next view from the view before it, and should only be able to navigate back to the previous view. Requiring multiple views.
Diagram: NavigationView - Email/Password View -- Onboarding Screen 1 --- Onboarding Screen 2 ---- etc
Where should I put the NavigationLinks to navigate to each view? Do they live in the root view switched with a variable? Or do they live in each successive view on the stack? If I try and put NavigationLinks in each view, Xcode complains that there's no NavigationView in said subview.
What's the correct way to implement this kind of navigation in SwiftUI?
Here is a very simple navigation from one view to other views and back again.
If you want complete control of the sequence, use the
NavigationStack(path: $path) {...}
with .navigationDestination(...)
as described in the docs and many SO posts.
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink("to View1", destination: View1())
}
}
}
struct View1: View {
var body: some View {
Text("This is View1")
NavigationLink("to View2", destination: View2())
}
}
struct View2: View {
var body: some View {
Text("This is View2")
NavigationLink("to View3", destination: View3())
}
}
struct View3: View {
var body: some View {
Text("This is View3")
}
}
EDIT-1:
Basic navigation using NavigationPath
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path){
Text("This is root")
Button("go to View1") { path.append("view1") }
Button("go to View2") { path.append("view2") }
Button("go to View3") { path.append("view3") }
.navigationDestination(for: String.self) { goto in
switch goto {
case "view1": View1(path: $path)
case "view2": View2(path: $path)
case "view3": View3(path: $path)
default: ContentView()
}
}
}
}
}
struct View1: View {
@Binding var path: NavigationPath
var body: some View {
Text("This is View1")
Button("go to View2") { path.append("view2") }
Button("go to View3") { path.append("view3") }
Button("go back to root") { path = NavigationPath() }
}
}
struct View2: View {
@Binding var path: NavigationPath
var body: some View {
Text("This is View2")
Button("go to View1") { path.append("view1") }
Button("go to View3") { path.append("view3") }
Button("go back to root") { path = NavigationPath() }
}
}
struct View3: View {
@Binding var path: NavigationPath
var body: some View {
Text("This is View3")
Button("go to View1") { path.append("view1") }
Button("go to View2") { path.append("view2") }
Button("go back to root") { path = NavigationPath() }
}
}