2

I'm trying to create navigation for my app using Navigation Stack and routing. My code is functioning and navigating to views, the problem I'm having is that the view is getting called several times from within a switch statement, I have placed the nav stack in the some scene view, then added a simple link, when tapped it goes through the switch statement and picks up the value 3 times and displays the view, I placed a print statement in the switch and it's printed 3 times for my new view value, following on with database calls etc, they are also getting called 3 times.

I'm new to SwiftUI so I'm sure it's user error, any help would be appreciated, thanks.

    enum Routing : Hashable {
    case AddFlight
    case PilotsList
    case newview
}


@State var navPath = NavigationPath()

var body: some Scene {
    
 
    WindowGroup {
        NavigationStack (path: $navPath) {
           
            NavigationLink(value: Routing.newview, label: {Text("Go to new view")})
            
         
                .navigationDestination(for: Routing.self) { route in
                
                
                    switch route {
                    case .newview:
                       Text("New View")
                         let a = print("New view")
                    case  .PilotsList :
                       PilotsListView()
                    case  .AddFlight:
                      
                          AddEditFlightView()
                    }
                    
                    
                }
        }
    }
}
Ravindra S. Patil
  • 11,757
  • 3
  • 13
  • 40
steve lang
  • 21
  • 2
  • I don't have an answer for you but I verified the behavior both in your code and some of my own existing case statements (XCode Version 14.0.1 (14A400)). Additionally the child view's init is called the same number of multiple times, but work in .onAppear() is only called the once. The number of extra calls seems to vary. It may have to do with the `Layout` system negotiating a size for the view, but that is just a guess. – carlynorama Oct 11 '22 at 19:40
  • Have also verified that it happens even when there isn't a case statement but a single possible ChildView, which makes me also suspect the `Layout` system since this closure is a `@ViewBuilder` This means that items that should only happen once should go in the .onAppear() code, it would seem. – carlynorama Oct 11 '22 at 20:01

1 Answers1

0

Putting this in an answer because there is a code "fix" for the reprints.

Verified the behavior both in your code and some of my own existing case statements (XCode Version 14.0.1 (14A400)). Additionally the child view's init is called the same number of multiple times, but work in .onAppear() is only called the once. The number of extra calls seems to vary. Have also verified that it happens even when there isn't a case statement but a single possible ChildView.

This makes me think it may have to do with the Layout system negotiating a size for the view. This closure is a @ViewBuilder which we can tell because we can't just put a print statement into it directly. It's being treated as a subview that's negotiating it's size with the parent. Which makes sense in hindsight, but wow, good to know!

This means that items that should only happen once should go in the .onAppear() code of the child view instead of inside of the destination closure which is a @ViewBuilder.

This code is fairly different than yours but that was mostly to check that the effect wasn't some quirk. The key is that it will only do the "onAppear" task once. Note that the perform closure for .onAppear is NOT a ViewBuilder, it is of type () -> Void. It is possible you might prefer .task{} to do an async database call, and that will also just run the once, it looks like.

struct ThreeTimePrintView:View {
    @EnvironmentObject var nav:NavigationManager
    
    var body: some View {
        NavigationStack (path: $nav.navPath) {
           
            Button("New View") {
                nav.navPath.append(Routing.newview)
            }.navigationDestination(for: Routing.self) { route in
                    switch route {
                    case .newview:
                        buildNewView().onAppear() { print("New View onAppear") }
                    case  .PilotsList :
                       Text("Pilot View")
                    case  .AddFlight:
                       Text("FligtView")
                    }
                    
                }
        }
    }
    
    func buildNewView() -> some View {
        print("New view")
        return Text("New View")
    }
}
import SwiftUI

enum Routing : Hashable {
   case AddFlight
   case PilotsList
   case newview
}

final class NavigationManager:ObservableObject {
    @Published var navPath = NavigationPath()
}

@main
struct ThreePrintCheckerApp: App {
    @StateObject var nav = NavigationManager()
 
    var body: some Scene {
       WindowGroup {
           ThreeTimePrintView().environmentObject(nav)
       }
    }
}
carlynorama
  • 166
  • 7
  • Thanks for the reply. If I use a Navigation Destination and label directly within a view as opposed to using the switch within the route view it only inits the once, I have tried calling a database function from .Task {} and this works well, and like .OnAppear, only fires the once. – steve lang Oct 12 '22 at 09:31
  • Great! So glad you found something that works! – carlynorama Oct 14 '22 at 22:11
  • Unfortunately `NavigationPath` is part of `SwiftUI`, which means it should not sit in the `ViewModel`, as you should not import `SwiftUI` there (unless you don't care about that). Was looking into using array of `enum`s, but so far getting some unclear error if trying to pass additional data like this `case somePath(String)`. – Jonauz Oct 31 '22 at 23:31
  • Did you post the problem as a question? I can take a look but just in a comment it is too hard to understand the trouble you're having. I do have an example for myself that uses enums with associated values here: https://github.com/carlynorama/NavigationExplorer/blob/main/NavigationExplorer/ViewTypePersistanceTest/NavigationManager.swift – carlynorama Nov 02 '22 at 19:29
  • In case it's not clear this is the associated view: https://github.com/carlynorama/NavigationExplorer/blob/main/NavigationExplorer/ViewTypePersistanceTest/RootView.swift – carlynorama Nov 02 '22 at 20:10