Navigation in SwiftUI

In this post I want to explain the difference between NavigationLink and DynamicNavigationDestinationLink in SwiftUI with a quick example and point out the differences between them.
You can find the sample code on GitHub

NavigationLink

If you followed some tutorials made in June 2019 you’d probably seen it called NavigationButton, but has been deprecated pretty soon and it is now called NavigationLink.
It is similar to a Button, but allows you to specify a destination and once the button is pressed the new View is pushed into the stack. Note that you have to be part of a NavigationView to use the NavigationLink, you don’t need to specify NavigationView in all of your views, but you can’t use NavigationLink if you’re not part of a navigation stack.
Let’s see an example


// coordinator.gestListOfCategories
// returns CategoriesList(dataSource: dataSource)
var body: some View {
        NavigationView {
            NavigationLink(destination:coordinator.getListOfCategories()) {
                Text("Show categories")
            }
        }
    }

so once you tap on Show categories a new view is pushed on the screen.
NavigationLink is great when you need to respond to a tap on the screen and you know where to go, but sometimes you want to show a new view programmatically, let’s see how.

DynamicNavigationDestinationLink

At the time I’m writing there aren’t many resources available about DynamicNavigationDestinationLink and that’s the main reason why I wanted to try it out, as I wanted a way to push a new view without necessarily having to use a Button, or a NavigationLink.
According to its comment, it is a DynamicViewProperty representing a collection of possible destinations in a navigation stack, generated and identified by data provided at presentation time.
So we have many possible destinations, and we can generate them at presentation time. Actually I can achieve a similar goal with NavigationLink, by creating the destination with a parameter, but as I said I’d always have to press a button and that’s not what I have in mind.
Let’s see how it works, you can find the full code here


var questionsList = DynamicNavigationDestinationLink(id:\Category.title.self) { category in
    QuestionsList(model:QuestionsListModel(category:category))
}

so questionsList is a variable in my struct, and it is a DynamicNavigationDestinationLink. It’s value can be changed by providing a parameter of type Category, and as you can see I can construct a new View with the category as a parameter.
This is an example of how to show a view via a dynamic destination link


var body: some View {
    List(dataSource.categories, id:\.title) { category in
        Button(action: {
            self.questionsList.presentedData?.value = category
        }) {
           CategoryRow(category:category)
        }
    }
}

So we have a List of categories, and there is a Button for each row.
By setting the value of presentedData into my DynamicNavigationDestinationLink variable I can execute the piece of code we saw previously, and instantiate a new QuestionsList view.
I now what you’re thinking, I told you I didn’t want to use a Button and actually in the example I could have used a NavigationLink instead and build QuestionsList directly inside it.
So what is the advantage of using DynamicNavigationDestinationLink? Suppose I have to perform a request before I’m able to show the category, like this:


var body: some View {
    List(dataSource.categories, id:\.title) { category in
        Button(action: {
            DataSource.performLongOperationOnCategory(category) { result in
                self.questionsList.presentedData?.value = result
            }
        }) {
           CategoryRow(category:category)
        }
    }
}

I can postpone the pushing of the new view until I receive the data, and on the meantime I could show some sort of activity indicator.
One other thing you can do with DynamicNavigationDestinationLink is programmatically pop the view by setting presentedData?.value to nil. The gist is you can set the value of presentedData at any time, after receiving something from a Publisher, after a Timer fires, or responding to a user action via a Button.

Hope this post was helpful, I’ll add more example in the future as I implement stuff in SwiftUI. Happy coding!