Teabyte

Mobile app development for iOS and Swift

Accidental Multiple Push with SwiftUI NavigationStack

2023-01-24

Currently I have the opportunity to work with SwiftUIs new NavigationStack view which allows easy programmatic push and pop. I have to say that it works fairly well for my use cases, which impressed me. But just recently I experienced a weird behavior where a simple detail navigation from a list of entries causes two detail views being pushed onto the stack instead of one. The solution to that issue in the end was very interesting.

Setup

The reproducible setup is quite small in this case. It offers a list of some string elements, which, when tapped on, should continue to push a detail view onto the stack.

struct ContentView: View {
 
    @State private var path: [String] = []
    let details: [String] = ["a", "b", "c", "c", "d", "d"]
 
    var body: some View {
        NavigationStack(path: $path) {
            List {
                ForEach(details, id: \.self) { detail in
                    NavigationLink(value: detail) {
                        Text(detail)
                    }
                }
            }.navigationDestination(for: String.self) { string in
                Text(string)
            }
        }
    }
}

The Bug

Now it gets interesting. When we actually execute the code, we can see the flow works for "a" and "b" but for "c" and "d" there are two or even three detail views being pushed onto the stack. Maybe some of you already knew what I want to point out next.

The main part of interest here is the NavigationStack. It needs a binding to a path. This path represents the navigation stack. Meaning, when we push an element to that stack, the .navigationDestination would be triggered, because we have defined to react on String types being pushed to the stack. Within the ForEach loop we have indicated that the identifiable part of our items in the list are the items themselves. Since our items in the list now contains duplicates, the navigation is actually triggered twice or three times even. Once for each element with the same identifiable value. This really caught me off guard when it was implemented in a real project. In that project it was also not that obvious that there were multiple elements with the same identifier, only when looking at the items in more detail, I could understand and reproduce that behavior.

Whats also a little bit funny, even Xcode gives us this hint, which unfortunately was covered in a lot of other debug messages in the real project...what a shame 🙈

ForEach<Array<String>, String, NavigationLink<Text, Never>>: the ID c occurs multiple times within the collection, this will give undefined results!
ForEach<Array<String>, String, NavigationLink<Text, Never>>: the ID d occurs multiple times within the collection, this will give undefined results!

Conclusion

What can we learn out from this example ?

  • Xcode does tell us about the certain issues we have in our code, sometimes these messages are just buried in the logs
  • Always make sure that when you use NavigationStack, NavigationLink and .navigationDestination with Identifiable objects, they are actually unique

I hope this small post gave some insights about a simple incident that might cause some frustration in a bigger project where weird things might not be that obvious in th beginning. At least for me, I learned a valuable lesson.

If you find any mistake or would like to reach out to me please do so 🙂

See you next time 👋