Search code examples
swiftuiswiftui-navigationlink

NavigationView usage in swiftUI


Coming from Android and working on a very complex application , i would like to use NavigationView as much as possible. Having one view and make all elements appear and disappear on this view seems impossible to handle for me . I was using navigationView to navigate bewteen views with navigationBar hidden . This way navigating or making view appear is transparent for the user

After some tests , i encounter limitations : at the 13th or 14 th level of navigation everything disappear and app basically crashes .

Once more , this is a direct navigation between 2 content views , no HOMESCREEN

import SwiftUI

struct test4: View {
    
    @State private var intent3: Bool =  false

    var body: some View {
      
        NavigationView{
  
            VStack{
                NavigationLink(destination : test3() , isActive : $intent3) { }
                Text("ver 4")
            .onTapGesture {
            intent3 = true }
                Spacer()
            }
            
        }
        .navigationBarHidden(true)
    }
    
}

import SwiftUI


struct test3: View {
    
    @State private var intent4: Bool =  false

    var body: some View {
      
        NavigationView{
  
            VStack{
                NavigationLink(destination : test4() , isActive : $intent4) { }
                Text("ver 3")
            .onTapGesture {
            intent4 = true }
                Spacer()
            }
        }.navigationBarHidden(true)    }
}

Here a basic example of navigation directly between 2 contents views . Crashes after 14/15 clicks. I encounter the same issue with about any navigation link.


Solution

  • Update: With your added code, I can see the initial crash was a result of adding a new NavigationView each time. This solves it:

    
    struct ContentView: View {
        var body: some View {
            NavigationView {
                Test3()
            }
        }
    }
    
    struct Test4: View {
        @State private var intent3: Bool =  false
        var body: some View {
            VStack{
                NavigationLink(destination : Test3() , isActive : $intent3) { }
                Text("ver 4")
                    .onTapGesture {
                        intent3 = true
                    }
                Spacer()
            }
            .navigationBarHidden(true)
        }
    }
    
    struct Test3: View {
        @State private var intent4: Bool =  false
        var body: some View {
            VStack{
                NavigationLink(destination : Test4() , isActive : $intent4) { }
                Text("ver 3")
                    .onTapGesture {
                        intent4 = true }
                Spacer()
            }
            .navigationBarHidden(true)
        }
    }
    

    Original answer:

    However, there are solutions to pop to the top of a navigation hierarchy.

    One way is to use isActive to manage whether or not a given NavigationLink is presenting its view. That might look like this:

    class NavigationReset : ObservableObject {
        @Published var rootIsActive = false
        
        func popToTop() {
            rootIsActive = false
        }
    }
    
    struct ContentView: View {
        @StateObject private var navReset = NavigationReset()
        
        var body: some View {
            NavigationView {
                NavigationLink(destination: DetailView(title: "First"), isActive: $navReset.rootIsActive) {
                    Text("Root nav")
                }
            }.environmentObject(navReset)
        }
    }
    
    struct DetailView : View {
        var title : String
        @EnvironmentObject private var navReset : NavigationReset
        
        var body: some View {
            VStack {
                NavigationLink(destination: DetailView(title: "\(Date())")) {
                    Text("Navigate (\(title))")
                }
                Button("Reset nav") {
                    navReset.popToTop()
                }
            }
        }
    }
    

    Another trick you could use is changing an id on a NavigationLink -- as soon as that happens, it re-renders and becomes inactive.

    class NavigationReset : ObservableObject {
        @Published var id = UUID()
        
        func popToTop() {
            id = UUID()
        }
    }
    
    struct ContentView: View {
        @StateObject private var navReset = NavigationReset()
        
        var body: some View {
            NavigationView {
                NavigationLink(destination: DetailView(title: "First")) {
                    Text("Root nav")
                }
                .id(navReset.id)
            }.environmentObject(navReset)
        }
    }
    
    struct DetailView : View {
        var title : String
        @EnvironmentObject private var navReset : NavigationReset
        
        var body: some View {
            VStack {
                NavigationLink(destination: DetailView(title: "\(Date())")) {
                    Text("Navigate (\(title))")
                }
                Button("Reset nav") {
                    navReset.popToTop()
                }
            }
        }
    }
    

    It works by marking the first NavigationLink (ie the one on the Home Screen) with an id. As soon as that id is changed, the NavigationLink is recreated, popping all of the views off of the stack.