Search code examples
swiftswiftuiswift3swiftui-navigationlinkswiftui-navigationview

Prevent NavigationLink to go back by itself to Navigation View


I have a list of restaurants that its observed all the time:

func getAllRestaurantsInParameters(lat: Double, long: Double){
        
        self.repo.getAllRestaurants().watch(block: {restaurants in
            
            self.restaurantList = (restaurants as! [Restaurant])
        })
}

In my main view, I have a NavigationView that has a list of NavigationLinks:

NavigationView{
            
     List{
         ForEach(restaurantList, id: \.self) { restaurant in
                             
                NavigationLink(destination: IndividualRestaurantView()){

                      ZStack {Text(restaurant.name)}
                }
         }
     } 
}

Whenever I enter whatever Navigaiton Link restaurant, and change something to another restaurant, the navigation link will go back by itself to the first main view.. Why that happens? I assume its because the observer method, that changes the restaurantList whenever a change happens, but still I dont want to go back to the main view..

How can I prevent this?

I did test to add .navigationViewStyle(StackNavigationViewStyle()) .navigationViewStyle(.stack) at the NavigationView, but did not work.

Any other options? The .watch(block: will observe any change of the restaurants and call that method.

How can I prevent the navigation link to go back to the navigation view by itself?


Solution

  • You're right in your assumption that the automatic navigation back to the main view is due to the observer in getAllRestaurants() triggering a change in restaurantList, which causes SwiftUI to re-render the view and reset the navigation stack. Here's a comprehensive solution to your problem:

    The issue occurs because every time your restaurantList gets updated, SwiftUI thinks it needs to update the whole view, which resets the navigation stack. To solve this, you can make your view more resilient to data changes by manually managing the state of your navigation links or by altering how you handle data updates. Here are a few strategies:

    Option 1: Using a stable identifier for NavigationLink

    If Restaurant conforms to Identifiable, use its id instead of .\self for a more stable identification. If Restaurant doesn't conform to Identifiable, you can add an identifier to your Restaurant model:

    struct Restaurant: Identifiable {
        let id: UUID = UUID()
        var name: String
    }
    
    // Then in your view
    ForEach(restaurantList, id: \.id) { restaurant in
        NavigationLink(destination: IndividualRestaurantView(restaurant: restaurant)){
            ZStack { Text(restaurant.name) }
        }
    }
    

    This way, even if restaurantList updates, SwiftUI can maintain the state of the navigation links because the identities of the restaurants haven't changed.

    Option 2: Avoid unnecessary updates

    Another approach is to minimize the updates to restaurantList itself. Only update restaurantList when necessary:

    func getAllRestaurantsInParameters(lat: Double, long: Double){
        self.repo.getAllRestaurants().watch { restaurants in
            // Convert fetched array to identifiable if not already
            let updatedList = (restaurants as! [Restaurant])
            if updatedList.map(\.id) != self.restaurantList.map(\.id) {
                self.restaurantList = updatedList
            }
        }
    }
    

    This code snippet ensures restaurantList is only updated if the fetched restaurant list actually differs from the current list based on the restaurant IDs, potentially reducing unnecessary view refreshes.

    Option 3: Use a different state management approach

    If the above methods don't fit well with your app's architecture, consider using a different state management approach like ObservableObject for your restaurant data:

    class RestaurantViewModel: ObservableObject {
        @Published var restaurantList: [Restaurant] = []
    
        init() {
            loadRestaurants()
        }
    
        func loadRestaurants() {
            // Simulation of data fetch and watching
            repo.getAllRestaurants().watch { [weak self] restaurants in
                DispatchQueue.main.async {
                    self?.restaurantList = (restaurants as! [Restaurant])
                }
            }
        }
    }
    
    // In your SwiftUI view
    @StateObject private var viewModel = RestaurantViewModel()
    
    NavigationView {
        List {
            ForEach(viewModel.restaurantList, id: \.id) { restaurant in
                NavigationLink(destination: IndividualRestaurantView(restaurant: restaurant)) {
                    ZStack { Text(restaurant.name) }
                }
            }
        }
    }
    

    Using an ObservableObject and updating the UI asynchronously can help manage state changes more cleanly and avoid unwanted navigation pops.

    By using one of these strategies, you should be able to control the unwanted navigation resets caused by data updates in your SwiftUI application. Since I don't know anything about your app's structure, ensure you pick the right options because it’s all about finding the right balance for your specific app’s architecture and needs.

    Hope this helps!