27 Feb, 2020
SwiftUI's lack of customization possibilities for navigation
At a fundamental level
SwiftUI it provides very little customization possibilities for any kind of navigation action. Whether you want to present a new screen as part of your normal navigation flow or as a modal of some kind separate from your main context.
This is a conclusion reached after trying to solve the very simple problem of presenting a full-screen modal. By a full-screen modal I mean the equivalent of the following (very normal) presentation style we are used to in
I do understand that
SwiftUI is supposed to represent a paradigm shift from the imperative to the declarative. In the specific case of a navigation action, we tell the framework that we want go from one
View representing a node in our App's flow to another but we are letting it decide HOW that happens.
I also understand that
SwitfUI code is supposed to be platform agnostic and portable between
WatchOS and this is perhaps why a full-screen modal presentation or a specific animated transition is not something that always applies.
Except this is one case where the HOW of how the navigation occurs is a subtlety that can carry a lot of meaning in the context of our Apps.
Beautiful, subtle animation throughout iOS builds a visual sense of connection between people and content onscreen. When used appropriately, animation can convey status, provide feedback, enhance the sense of direct manipulation, and help users visualize the results of their actions.
This is why I do hope
SwiftUI comes to a middle ground where we can influence or determine how navigation action presentation and transition is actually done even if limited to specific contexts, for example: If a new node in our flow is presented full-screen or not and what animation to use, if that is something that applies to a specific device.
Our specific example of a full-screen modal presentation (with customizable animation) remains unsolved using only
SwiftUI. That being said, there are a number of workarounds, each one with their own problems that can be used if you want to achieve this today.
Without further ado I would like to present the alternatives that need to be employed to achieve a full-screen modal presentation at the time of writing using
How to present full-screen modal in
SwiftUI + UIKIt Hybrid
By far the best solution and it's a pity that at its core it means totally bypassing
SwiftUI and reaching out to
UIKit to solve the problem for us.
This solution was created by stackoverflow user Mojtaba Hosseini in this question and all credit goes to him.
One way to achieve a full-screen modal presentation is to create an
EnvironmentKey that holds the value of
UIApplication.shared.windows.first?.rootViewController and then access this from
SwiftUI to call
UIKit methods such as
dismiss() on that
A small extension on
UIViewController is needed to create a way for the
UIViewController to present an arbitrary
By using this approach, one can leverage the existing presentation API's from
UIKit while still having View instances made with
This solution is good and it works like a charm for devices that support the
UIKit framework but will not work at all for
AppKit and this is the risk we run by bypassing
- Fulscreen modal presentation on iPad.
- Ability to use UIKit presentation APIs.
- Not very intuitive
- Not full SwiftUI
- Specific to
UIKitmeaning that porting to
WatchOSrequires extra work.
SwiftUI Conditional View
I had already written about this approach on a previous article and its worth revisiting. To recap, this approach involves using a simple
if statement to show or not our "modal" on top of our "base"
View and it looks something like this:
This approach looks the part, but it has a few problems.
The biggest issue I have with it is that we are only cosmetically transitioning from one
View to another.
SwiftUI does not conceptually know that this is a totally new "Screen" or "node" in a navigation flow and as such it not officially a navigation action.
As a consequence, unless you manually hide the source/parent Screen all its view hierarchy will still be present creating optimization and accessibility issues.
We can solve the issue by presenting the "modal" instead of, rather than on top of the base view. We can also add a bit of animation while we are at it.
Solved. Right? Well... not really. This solution doesn't scale very well. Imagine having to write this piece of
if statement for every
View you want to present.
SwitfUI we can use a few tricks to create a module out of this using a
Which we can then apply like this, for a much more scalable solution:
Problem solved right? well ... no. This works really well until our base view is not the root view on screen. For example when it's presented inside a
And this is why this solution only gets you so far.
SwiftUI doesn't know that this is supposed to be a new screen or node in our app and it doesn't treat it as such. Because well, it really isn't.
I have anyway worked out this
ViewModifier in this Git repository: https://github.com/piterwilson/SwiftUI-FullscreenModalViewModifier
Use it if it helps.
- Fulscreen modal presentation on iPad.
- Full SwiftUI solution
- Not really a modal presentation(?)
- You will have to add your own animated transition.
- In general it ceases to be a full-screen modal when the modifier is added to a
Viewthat is not the root View in a given layout.
It is possible to achieve the effect of a fullscreen modal by creating a NavigationLink inside a NavigationView on the iPad provided that:
.navigationViewStyle(StackNavigationViewStyle())is applied to the
- The destination view uses
- The destination view uses
This will create a slide transition and it will be full-screen. At the time of writing it seems to be impossible to customize the transition of a
- Full SwiftUI
- Animation not customizable
To sum it up, when looked up close like this, it remain disappointing that transitions and presentations of different nodes in an App's navigation are so difficult to customize. This results in workarounds and cosmetic solutions which cause all kinds of problems.
SwiftUI is indeed a young framework and hopefully we will see some evolution in this respect.
Sources and Further reading
- Piterwilson on Github, SwiftUI FullscreenModalViewModifier, https://github.com/piterwilson/SwiftUI-FullscreenModalViewModifier
- Piterwilson on Github, SwiftUI Fullscreen modal https://github.com/piterwilson/SwiftUI-Modal-on-iPad
- Apple, SwiftUI View documentation, Retrieved from
- Apple, Modality, Human Interface Guidelines, Retrieved from https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/modality/
- Apple, Animation, Human Interface Guidelines https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/animation/
- Apple, EnvironmentValues, Retrieved from https://developer.apple.com/documentation/swiftui/environmentvalues