Search code examples
iosswiftxcodeswiftuinavigationview

How to create multi-view deep NavigationView in SwiftUI?


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?


Solution

  • 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() }
        }
    }