NavigationStack + navigationDestination issue in

Hi everyone, this is my first post. I'm facing an issue that I don't fully understand. The problem started once I migrated to use NavigationStack instead of navigation View.

I have a View, let's called "NextWeekView" where I do some async process onAppear, calling an async function from the "NextWeekViewModel". Depending on the result of this processing, I need to navigate automatically to different Views. I use @Published variables on the ViewModel that I populate with the destination view. Once the processing is finish, on the View and still on onAppear, I enable a @State navigate, and return the destination View based on the @Publish:

The View

enum DestinationType {
    case play
    case menu
    case endseason
}

struct NextWeekView: View {
    @ObservedObject var team: Team
    @StateObject var nextWeekViewModel = NextWeekViewModel()
    @State private var destination: DestinationType? = nil
    @State var navigate: Bool = false

    var body: some View {
        LoadingScreenView(text: self.nextWeekViewModel.currentStep)
            .navigationDestination(isPresented: self.$navigate) {
                destinationView(for: self.nextWeekViewModel.destination)
            }
        .navigationBarHidden(true)
        .statusBar(hidden: true)
        .onAppear{
            Task {
                await nextWeekViewModel.processNextWeek(team: team)
                if nextWeekViewModel.finishedProcessing {
                    self.navigate.toggle()
                }
                print("navigate to: \(self.navigation)") // debug, it always print the expected value. 
            }
        }
    }
    // Function to return the appropriate view based on the destination
       @ViewBuilder
       private func destinationView(for destination: DestinationType?) -> some View {
           switch destination {
           case .play:
               MatchPlayView(team: self.team)
           case .menu:
               GameMenuView().environmentObject(team)
           case .endseason:
               SeasonEndView().environmentObject(team)
           case .none:
               EmptyView()
           }
       }
}

The ViewModel

class NextWeekViewModel: ObservableObject {
    var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    @Published var currentStep:LocalizedStringKey = ""
    @Published var destination: DestinationType? = .menu
    @Published var finishedProcessing: Bool = false

    @MainActor
    func processNextWeek(team: Team) async {
        ....
        
        if currentWeek.matchdays.filter({ md in return md.num > currentWeek.current_matchday }).count == 0 {
                       await self.endWeekProcess()
            // We check if it is end of season
            if currentSeason.current_week == currentSeason.weeks.count && currentWeek.matchdays.filter({ md in return md.num > currentWeek.current_matchday }).count == 0 {
                // End of season
                await self.endSeasonProcess(team: team, currentSeason: currentSeason)
                self.destination = .endseason // WORKS 
            } else {
                // We go next week
                self.currentStep = "Create cash operations"
                await self.createCashOperations(team: team, week: currentSeason.current_week)
                
                self.currentStep = "Create messages"
                await self.createMessages(team: team)
                self.destination = .menu //WORKS 
            }
        } else {
            print("Ther are more matchdays, lets pass the matchday")
            // THIS DOES NOT WORK
            // Any Modifications of destination is reflected on the view, but automatic navigation is not taking place
           self.destination = .play 
           //self.destination = .menu // this also do not work.
        }
        self.finishedProcessing = true
    }

What's happens: The code works when app goes through code path marked as //WORKS but not when follows code path //DO NOT WORK. Interestingly, it works perfectly on iOS 18. But not in iOS16/iOS17.

If I add an interactive NavigationLink instead of the navigationDestination in the View :

 if self.nextWeekViewModel.finishedProcessing {
                NavigationLink(destination:destinationView(for: self.nextWeekViewModel.destination)){
                    SimpleButtonWithIconView(icon: "chevron.right.2", text: "Next")
                        .padding(.bottom, 10)
                }
            }

Then it works as expected but I really want to avoid the user to tap Next for a seamlessly gameplay

I've tried all combinations of state variables, using different state variables per destination, but I always arrive to the same situation.

I also printed everything left and right and View values looks consistent so I think the ViewModel is doing the right thing (also with the NavigationLink it works). It's simply that the "automatic" navigation does not want to navigate in certain occasions.

I'm completely out of ideas. Any feedback will be welcomed. Otherwise I will revert back to NavigationView.

Thank you in advance!

My initial suggestion would be for you to:

  • Migrate to Observation instead of using @ObservedObject and move your logic from the .onAppear modifier to the .task modifier. onAppear/onDisappear are about view lifecycle, and doesn’t equate to view visibility.

  • You could also restructure how func processNextWeek(team: Team) might be better modeled to have it return the Boolean value. The alternatively, is to use onAppear + .onChange modifier to observe nextWeekViewModel.finishedProcessing. This would ensure that your SwiftUI view observes the value and you can then enqueue an update to your Swift state variable.

These are just suggestions and you can give those a try—if you can provide a focused example, it’ll be easier to understand the issue. If you're unfamiliar with creating a test project see, Creating a test project.

Thank you, I will try those approaches and return with the results!

Accepted Answer

Hi, just an upgrade to close the topic. Moving to Observation is not an option as it will force me to drop iOS 16 support and I would like always to maintain support for minimum 3 iOS versions.

I also tried suggested on point 2, among things but nothing worked.

Finally, I analysed other parts of the code where I had something similar but worked, and came out with the fact that only had this issue when the work on .onAppear was fast. In other screens where I do some processing onAppear, it worked.

So I ended adding a subtle sleep inside .onAppear which "fixed" the issue.

        .onAppear{
            Task {
                await nextWeekViewModel.processNextWeek(team: team)
                if nextWeekViewModel.finishedProcessing {
                   
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        self.navigate = true
                    }
                }
            }
        }

It's not ideal but it seems that the underlying problem is fixed in iOS18 so I accept this as a workaround for iOS16/IOS17.

Thanks for the help.

NavigationStack + navigationDestination issue in
 
 
Q