Introduction
After a long hiatus from releasing new blog content, I'm back with iOS content.
I've gained valuable insights into iOS and Swift Programming at the Apple
Developer Academy. Now, with the iOS 17 updates, we have a new Observable
macro and Observable Framework. Additionally, the latest navigation system
introduced in iOS 16, NavigationStack
, is quite robust, and we'll leverage
that to our advantage.
NavigationStack
usage
The usual implementation of NavigationStack
is that you can put
NavigationLink
to allow user to navigate by pressing it.
In the example above, we set up a basic master-detail setup. We plant the
NavigationStack
at the top of our view setup. Then, we list out messages, each
serving as a link to its details screen. Notice, we're sticking with the
old-school NavigationLink
type, and it does the job just fine here.
Navigator Pattern
I enjoy organizing my feature's navigation flow in one central location. That's why I typically use the Navigator pattern to manage navigation in a type-safe manner. Implementing the Navigator pattern with SwiftUI's new data-driven Navigation API is straightforward. Initially, we need to establish an enum type that outlines all the routes for our app, feature, or module.
Next up, we define the view based on the route we define earlier
After that, we should create a single controller for the navigation. We can do
this by using a ViewModel. Remember when I said earlier that we can utilize the
new Observable Framework? We will define the ViewModel
by using the
Observable
macro.
Looks like pretty lengthy code, doesn't it? The navPath
variable is the source
of truth, storing the list of routes. It is an array of route and we can modify
it like any other array. We've defined some methods there to assist us in
navigating and modifying the navPath
itself. This ViewModel
will be used
globally across the app, so we should create an Environment
.
Defining the Environment
To define the navigate
Environment, we can extend the EnvironmentValues
from
SwiftUI. But first, we need to define the EnvironmentKey
with the defaultValue
of the ViewModel
itself.
After that, we can extend the EnvironmentValues
. I'm naming it navigate
but
you can use any other names as you like.
Now we can use the environment globally. Next up we need to use the ViewModel
in our NavigationStack
. In this implementation we are going to do it in the
App
struct.
Implementing the NavigationStack
We must use Bindable
macro instead of State
because NavigationStack
needs
to bind the NavigationPath
.
We now have exactly one NavigationStack
wrapping the ContentView
, and we
attach the navigationDestination
view modifier and use our previously defined
Routes struct to map the view inside of the NavigationStack
. Don't forget to
use the environment
view modifier to be able to access the view model inside
the children view.
Usage
You should note that in the previously defined Routes mapping, there are several views. Now, we will use it to demonstrate our new navigation system. The view itself isn't very complex but is sufficient for demonstration.
When you run the app, the first view will be presented. Press the button to
navigate to the second view. Now, try interacting with all the buttons and see
how robust it is. You don't need to use NavigationLink
all over the place;
just call the navigate
method. You can also call this inside any functions or
triggers.
Of course, the source code is available here on GitHub.
Conclusion
I'm thrilled to use the new data-driven Navigation API in SwiftUI. I hope you enjoy the post. Feel free to contact me and ask your questions related to this post. Thanks for reading, and see you in the next blog!