SwiftUI a navigationDestination was declared earlier on the stack when pushing new value to the NavigationStack – Swift

by
Ali Hasan
axios swift-concurrency swiftui-animation swiftui-navigationlink swiftui-navigationstack

Quick Fix: Move all navigationDestination log somewhere that is not repeating. So .navigationDestination(for: SomeProfile.self) will not be created again and again.

The Solutions:

Solution 1: Move Navigation Destination Out of Repeating Views

In the provided code, you’re adding the .navigationDestination(for: SomeProfile.self) {...} modifier inside the FollowersList view, which is repeated each time you navigate to a follower’s profile. This causes the error because you’re adding the same destination multiple times on the stack.

To fix this, move the navigation destination out of the FollowersList view and place it directly inside the NavigationStack instead. This way, it will only be declared once and will apply to all views within the NavigationStack.

NavigationStack(path: $path) {
    VStack {
        Text(myProfile.username)
        Button("See followers") {
            path.append(ViewOptions.followers(myProfile.id))
        }
        // Move the navigation destination here, outside of repeating views
        .navigationDestination(for: SomeProfile.self) { profile in
            switch profile.isMe {
            case true: Text("This is your profile")
            case false: SomeProfileView(path: $path, profile: profile)
            }
        }
    }
}

This approach ensures that the navigation destination is declared only once closest to the root view of the stack, preventing the error from occurring.

Solution 2: Nested NavigationDestinations

In this approach, we utilize an Options enum to define the possible destinations for the navigation stack. We place the .navigationDestination modifiers outside of any repeating views to ensure they remain persistent throughout the navigation stack.

enum Options: Hashable {
    case destination(Int)
}

struct MyProfileView: View {
    ...
    
    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                ...
                
                Button("See followers") {
                    path.append(Options.destination(id))
                }
            }
            .navigationDestination(for: Options.self) { destination in
                switch destination {
                case .destination(let userID): FollowersList(id: userID)
                }
            }
        }
    }
}

struct SomeProfileView: View {
    ...
    
    var body: some View {
        VStack {
            ...
            
            NavigationLink("See followers", value: Options.destination(id))
        }
    }
}

By following these changes, the issue of navigation destinations being declared multiple times on the stack is resolved, ensuring that the correct destination is used for the navigation operation.

Solution 3: Refactor Code Structure

Refactor the code structure to avoid declaring navigation destinations multiple times in the navigation stack. Specifically:

  1. Use a more specific type (e.g., SomeProfile) instead of Int for the NavigationLink value to represent the follower profile directly.
  2. Update the NavigationLink in FollowersList to use the follower profile directly instead of its username.
  3. Modify the NavigationStack in MyProfileView to pass the SomeProfile of the selected follower instead of the id.
  4. Remove the navigationDestination modifier from the Button in MyProfileView.

By following these steps, the code avoids redeclaring navigation destinations and ensures that only the intended destination is used when navigating.

Q&A

What is the error: A navigationDestination for was declared earlier on the stack when pushing new value to the NavigationStack?

You are repeatedly adding an identical .navigationDestination(...) modifier. Move them outside of repeating views.

Is it better to use Int or a more custom type as the value for NavigationLink?

Yes, it’s better to use a more custom type, such as an enum, to represent the destination options.

How can I fix the error related to multiple navigationDestination declarations?

Move the navigationDestination modifiers outside of repeating views and pass the correct data.

Video Explanation:

The following video, titled "Look At How Easy Navigation In SwiftUI Is Now!!! | A Complete ...", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

Unit Tests In Swift for our RouteManager 01:27:57 - Summary - SwiftUI Programmatic Navigation ... This NEW SwiftUI Feature Is AMAZING | Navigation ...