Why messy?
It's because of the core idea of SwiftUI — a view is a function of the state, or a view is state-driven. Don't get me wrong, this concept is great, but SwiftUI's navigation is not this advanced yet.
However, SwiftUI does not have the means to construct robust navigation inside your app.
Messy example
Consider the common case of the onboarding screen when you need to present some sequence of views with nice transitions. What can you do with SwiftUI? Probably, create an enum
that tells which screen is active and then use switch
to present the sequence of views.
What if you need to modify the order or change the number of views? You'll need to modify the corresponding enum
, modify the logic of switching inside the views, and other stuff.
Not so flexible, right?
Oh, and then you decide to present one view right in the middle through .sheet
. That's when the mess starts to show up. You create an additional @State
to check if the sheet is open, make sure that it's updated correctly, and restructure the switch
block that you used before.
Now, it's a chaotic view that is prone to unexpected bugs.
Existing navigation views
The most obvious one is NavigationView which is deprecated in the new iOS 16.
Using NavigationLink
, it can present new views and also adds a "back" button to return to the previous view.
And it does not support programmatic navigation.
Apple presented a new NavigationStack that addresses this issue but it is still not flexible enough. For example, I like to have the ability to modify the view whatever I want, but NavugationStack inserts back buttons. Also, it does not support different transitions. While it is nice to see SwiftUI develop in this direction, yet we are not there.
So, even in iOS 16, SwiftUI is not powerful enough to manage any kind of navigation you can come up with.
And .sheet()
. NavigationStack
does not make it easier to handle .sheet()
either.
Designing a flexible navigation library
I decided to create a library with several requirements:
- Programmatic views navigation
- Ability to present a sequence of views
- Support for any SwiftUI transition and Animation
- Completely state-driven: no singletons or environment objects
- Handle
.sheet()
Sounds cool, right?
Straight to the point, I was able to create such a library.
So, if you just want a nice tool for the things I listed above, you can stop here. Now, let's see how I did it.
Ways to present
At the core of the library is a structure that stores views and information about how to present them. Possible options for presentation are
enum PathType {
/**
* Just show a view. No animation, no transition.
* Show view above all other views
*/
case plain
/**
* Show view with in and out transitions.
* Transition animation also can be specified.
*/
case animated(transition: AnyTransition, animation: Animation)
/**
* Show view in .sheet()
*/
case sheet(onDismiss: Action)
}
.sheet()
is as easy as just presenting any other view.So, you can present the view without any animation, present it with needed transitions, and present it in a sheet.
Path
This structure stores information about views. It just stores an array of type-erased views with presentation type information. You can append views on top and remove them from the top.