Making a Real World Application With SwiftUI
Part three: navigation and presentation
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
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
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
A view for presenting a stack of views representing a visible path in a navigation hierarchy.
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.
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.
How to Programmatically Push and Pop Views in SwiftUI with NavigationDestinationLink | Ryan…
The *Link views are fine options if you don't need to programmatically dismiss the modal or navigation. The…
Presentation is for everything presented modally, and it can be a view hierarchy (which you can compare to a UIKit
presentViewController) but also for
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
It’s exposed by SwiftUI — you can also toggle — useful to dismiss your modal within itself without forwarding any callback.
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.
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.
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.
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!