Navigation in SwiftUI | Custom and Complete with NavigationStack

Moussa Hellal
6 min readJun 25, 2023

First of all I am absolutely thrilled and honored by the many requests I received from the iOS community to create this article following my latest piece on how the navigation works in iOS 16 with SwiftUI. Without further ado, let’s get into it.

Since Navigation had a huge shift in iOS 16, and the old way of navigation has faced deprecation.

It is time to get into Navigation and understand its ins and outs. In a previous article I explained in details on how to do a basic navigation between two screens.

Approaching the topic of Navigation in SwiftUI is like approaching a hidden cave that might just have many hidden and not fun stuff, you might be asking these hard questions to yourself already:

  • How to do custom navigation? in My app I have like 30+ screens and more?
  • How can I separate Navigation logic from my views?
  • What If I need some data in my 20th screen, that only exists in my first screen?
  • How can I get back to a specific screen or to root screen If I wanted to do?
  • What if I got 5+ complex objects that got many properties that I want to move through different screens?
  • and Many endless questions that many iOS developers and the community are asking and many are struggling to find a solution especially with the latest navigation APIs that apples has introduced to in the navigation.

No Worries! Good News!

Today we are going to learn how to do navigation between 20+ screens if you want to. We are going to learn how to handle these point:

  • Complex and Custom Navigation in real world scenario including navigating between 3+ screens at a time.
  • Moving whatever data structure you want from one screen to another.
  • Back to previous screen.
  • Back to main a.k.a Root screen.
  • How to take advantage of the power of @EnvironmentObject in SwiftUI in dealing with navigation.

let’s start out with the basics of NavigationStack.

The New Navigation in iOS 16, has few things to take in mind:

NavigationsStack

this component is responsible for the root view and presenting (navigating) to other views on top it.

NavigationPath

data container that maintains a record of the content being shown on various screens or views.

navigationDestination(for: destination: )

This function is in charge of connecting a destination view with a certain data type that it displays, enabling its usage within a navigation stack.

Let’s get into it:

Our project that we are going to use is an app that lists few drinks and a Drink Detail and Ingredients views.

Out starter project have baisc three views:

  • DrinkyView — Contains the list of drinks.
  • DrinkyDetailView — Contains the drink details.
  • DrinkyIngredientsView — Contains the drink Ingredients.

Project Link:

https://github.com/MoussaHellal/CompleteNavigationSwiftUI , download and open the Start with Project folder to follow up with me.

The result will look like this : Exciting I know! Hence, let’s get the fun going!

Now let’s open up the Start with Project folder. If you want you can also check Final Project which has the final result of this article and you can continue to understand how we implemented the navigation with me here.

First thing we need to add the navigation logic to our Project.

Make sure to Create a new folder named “Navigation” and create the three files under this one 👇:

DrinkyNavigation.swift

Simply an Enum that holds all our navigation cases, in our case we have two different views we can navigate to. In your case you can add as many as you need.

DrinkyViewFactory.swift

Most of the magic happens here, this is our view factory that accepts our destination and set the View we are going to navigate to. Simple! Isn’t?

But maybe you are starting to wonder why our views here does not accept any parameter? How Can I pass data then? Great questions! And the answer is here:

DrinkyFlow.Swift

Voila! Our main Navigation holder is here and will be responsible for holding :

  • Passable data between our views (in this example we have selectedDrink which is initialized as empty Object for simplicity sake, you can later introduce it as an optional and handle other cases…)
  • Path of our Navigation.
  • Back to root.
  • Back to previous screen.
  • Clear out our navigation.
  • and Also we can introduce as many other functions as you want, according to your app needs.

Now since we added our Navigation tools that we need in our application now we can move to modify our views:

Open up DrinkyView which holds our main screen and list of drinks, once we click we need to move to Drink Details view.

  1. First we add our navigation flow, which we named here drinkyFlow as @EnviornmentObject this we are gonna pass later from our main function but for now let’s complete editing this file.
  2. We need to add our NavigationStack and pass the NavigationPath that we have introduced in our DrinkyFlow.
  3. Upon clicking on any item of our list of drinks we need to move to its details view while also setting our object that the user wants to see.
drinkyFlow.selectedDrink = drink // WE ARE SETTING OUR SELECTED DRINK
drinkyFlow.navigateToDrinkyDetailView() // WE ARE MOVING TO DETAILS SCREEN

As you can see here we are simply calling navigateToDrinkyDetailView() and before that we are setting our selected drink that we are going to use in next screens.

4. Finally, we add our .navigationDestination modifier, this will be responsible for navigating to our destination through DrinkyViewFactory and DrinkyNavigation enum.

Open up DrinkyDetailView which holds our detail drink screen.

  1. First, we make sure to add the drinkyFlow EnvironmentObject, as you notice here all the power resides in this property wrapper for sharing the data between our views.
  2. Let’s change our views for it to be using the selected object from DrinkyFlow.
  3. Lastly, we add a button to navigate to Ingredients Screen.

Lastly, Open up DrinkylngredientsView which contains our Ingredients view screen.

  1. First, as usual we add the drinkyFlow EnvironmentObject.
  2. Let’s change our views to be using the selected object from DrinkyFlow.
  3. Lastly, we add a button to navigate back to home.

One last step, Open main function to pass our shared Object.

That’s it, Run the App!

! Notice: System Back button here can be hidden, and you can use whatever button you see fit best to your design and the you can use our cutom back to previous screen method.

drinkyFlow.backToPrevious() 

Voila! You did it, you can always refer back to Project link in Github for Final result if you have found any issues:

You can expand this solution to 30+ screens navigation. Use with caution and create more separate Navigation objects if needed be.

You can create as many as NavigationFlow you need, just replicate that navigation folder and change naming for different modules, this is needed especially if you get tabbed view app that has many different independent Features. Also to keep your app cleaner and more manageable when it comes to navigation.

Time for Fun! experiment, change stuff, add more screens and more objects and Enjoy the process.

I appreciate you choosing to spend some of your day with me. I hope that my piece was informative and helpful to you.

Farewell, keep growing and learning. See you in the next one. ✌️

Make sure to follow me on Twitter to keep receiving the newest articles:

https://twitter.com/codeWithMoses

--

--