Part three: navigation and presentation

Thomas Ricouard
Jul 16 · 6 min read
Application navigation example

This story is the third part of a collection of articles about making a SwiftUI application. I recommend you to read part one and part two first; they’ll give you an overview of the application, its capabilities, features, and architecture.

In this article, we’ll see how the navigation and presentation flow works in SwiftUI. After almost ten years of development with the current UIKit model, this has been both a breath of fresh air and conceptually quite hard to grasp. In the end, it’s simple, you’ll just need to let go of any concept of a UIKit imperative way of doing navigation.

The first concept you need to lose is that every new full-featured view should be modal or pushed on the stack. This is important because SwiftUI is much more flexible than UIKit, and in a few lines of code you can append a whole view hierarchy on top (or below) your current one.

You don’t need to addChildViewController, addSubview, or add the constraints, etc., which is why UIKit often makes it much simpler to present a modal or push a new controller on the UINavigationController stack than to actually try to make it work with your current view hierarchy. I’m not talking about containedViewController which is a nice pattern and a perfectly good way to compose big UIViewController.

A Little Introduction to Navigation and Presentation


NavigationView is the equivalent of UINavigationController, at this point in time, it’s still even backed by one, but there is absolutely no guarantee it’ll still be the case in a few months or a couple of years. You should not really care about the underlying implementation. In SwiftUI, you need to see everything as a contract. The navigation flow guarantees you it’ll be able to stack views on top of another, with a back button and a title, whatever the platform it’s available on.

For example on macOS and iPadOS, a NavigationView supports two views as its root. It’ll automatically convert to a UI/NS/SplitViewController as it’s rendered, with the master/detail pattern. You can inspect the view hierarchy and see that for yourself from Xcode.

This abstraction level means that Apple is responsible for making SwiftUI primitive components work, whatever the platform they’re available on. And they can swap the implementation whenever they want while keeping the component top-level API unchanged.

But let’s move back to our NavigationView.

If your current view — or whole — app is wrapped into a NavigationView, you can use NavigationLink anywhere you want.

It takes a destination view and a body. The body is what’s displayed in the content of your link (can be anything View related), and the destination is what view is pushed on the stack when you click on the button.

The whole view inside the content of your NavigationLink will be interactive. And in the case of a List, if you wrap the NavigationLink around the view you use as a row, you’ll get a nice detail indicator with an active/hover state.

NavigationLink examples

There is also DynamicDestinationLink, in case you don’t know what data will be passed to your detail view until later in your view master view. With this, you can dynamically set up your DetailView and change the bound data at will. I didn’t really have the opportunity to use it within my app, but here is a very nice tutorial about it.

And one more thing, here is another tutorial. If you want to programmatically push a view, you can use NavigationDestinationLink — useful if you want to do a router for example.


Presentation is for everything presented modally, and it can be a view hierarchy (which you can compare to a UIKit presentViewController) but also for Alert, ActionSheet, etc…

Unlike navigation, as SwiftUI is declarative, you’ll have to manage the presentation state of your presented views. It’s very seamless; you create a @State var isSomethingPresented: Bool property, and in the .sheet(), .actionSheet(), etc.. function, you can pass it as a binding. So you can control if your sheet (you can compare it to a full view controller presented as a modal) or you actionSheet should be presented.

Then you just have to toggle it wherever it pleases you, as the user touches the button that is supposed to present the modal/sheet or the ActionSheet, for example.

In the code above, you can see two examples of presentation. One is an action sheet, with a binding to its presentation, the other is a presentation of a full screen sheet/modal view. Then if you look at the onAddButton() function, it toggles a boolean, and my action sheet will effectively be presented. You would need to do exactly the same to present the modal. That’s all you need.

Don’t forget to toggle the boolean in the buttons of the ActionSheet to dismiss it.

Within the view that is presented as part of any of a presentation call, you can access an Environment variable to access its isPresented state.

It’s exposed by SwiftUI you can also toggle — useful to dismiss your modal within itself without forwarding any callback.

About Environment, I invite you to follow this Apple tutorial about working with controls. It’ll shine some light about editing the state Environment variable which is very powerful.

There are various Environment properties that SwiftUI exposes. You can access any of them with their keypath. For now, you can look at this documentation; they’re not yet all referenced. The best source is still searching within SwiftUI prototypes to find them.

Other Presentation and Transition Methods

As I was saying above, you’re don’t necessarily need to use the Navigation or Presentation pattern to toggle or present views.

To illustrate my point, here is how I present my carousel in the movie detail screen.

It’s toggled as soon as you touch a movie alternate poster.

And now we’ll look at the code.

Extract of MovieDetail view.

Above is a part of my MovieDetail view. The presentation effect is done in two parts and controlled by the @State var SelectedPoster property. Once it’s set and not nil, the List will be blurred. Then the ImageCarouselView will be presented at the middle of the screen.

The ImageCarouselView is a ZStack that fill the bounds of the screen, and it embeds a ScrollView as its center. And that’s it.

I can add it “over” my list because my MovieDetail view has a ZStack as its root. So you can stack any number of view you want. You can even use the zindex() modifier to play with the hierarchy.

On top of that, in SwiftUI I get this nice fade in and fade out of the blur effect because I’ve added the animation modifier on my List. As a whole, you feel like the carousel is “presented” on the screen, but it’s just two simple effects toggled at the same time.

You can view the code of the ImagesCarouselView on the MovieSwiftUI repository here.

In the UIKit world, it would have been simpler to present a full-screen UIViewController and then implement a custom transition, or put a UIVisualEffectView as a background view to get the blur effect. Then set up the presentation context to be the current one… well, you know the drill.

As a note, you can also do custom transitions animations as views appear and disappear with the transition modifier, but it’ll be covered in another article. I use it mostly to transition small views on and off the screen. But you can follow this Apple tutorial and start to play with it!

I hope this article has been useful to you, and as always, I’m here to answer any questions.

Stay tuned for part four: animations and gestures!

Better Programming

Advice for programmers.

Thomas Ricouard

Written by

📱 🚀 🇫🇷 [Entrepreneur, iOS/Mac & Web dev] | Now @glose 📖 | Past @google 🔍 | Co-founded few companies before, a movies 🎥 app and smart browser one.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade