At last, I have achieved a version of the Router pattern that I am happy with and addresses the problems I intended to solve when I started.
Remove navigation specific APIs from SwiftUI views.
Prevent views from knowing about other views and how to build them.
Shield views from knowing how other views are navigated to.
I've open sourced my library, called Routing, on Github. It is the result of all the exploratory work and iterations I made in previous posts:
Removing navigation APIs from SwiftUI views
The first step was figuring out how to remove any SwiftUI navigation code out of my views. To solve this I created a RoutingView that acts as a wrapper for the view hierarchy.
It contains the SwiftUI navigation APIs for supporting stack navigation, sheet presentation, and full screen cover presentation.
Additionally, it is responsible for creating the Router instance that gets injected into the root view and is used to manage navigation within the RoutingView.
This RoutingView is only responsible for containing the SwiftUI navigation APIs and utilizing the Router object to build views.
Moving navigation logic to the Router
The Router object is responsible for acting as the gateway to navigation for individual views. It contains the @Publshed properties that the RoutingView observes to trigger the appropriate navigation action.
All views have to do is call the routeTo(_) method and the Router will do the rest. It hides how views are created and navigated to (ie: via navigation stack, sheet, full screen cover).
The object also provides other useful methods for manipulating the navigation stack and dismissing screens programmatically.
Modularity
Finally, the Routable protocol was created to allow implementers to handle specific view creation as well as declare how views should be navigated to. As a result, this removes the previous dependency the Router had on the view code, which existed because the Router had to know how to build views.
This would lead to a circular dependency where the Routing package would need to know about code outside of it, namely the views it would navigate to. With the introduction of the Routable protocol, the Routing library has no dependency on any outside code, making it perfect for modularized code bases. Below is the Routable protocol definition:
Here is an example of how client code can implement the Routable protocol to define what views are used and how they are navigated to:
Conclusion
I have enjoyed building this little library and plan on continuing to improve it. If you have any suggestions for improvement or wish to contribute, please do not hesitate to do so by either contributing directly to the Github repository or by contacting me via my contact form.
If you enjoyed following along this journey, you can subscribe to my newsletter to get more insights into iOS development and be amongst the first to hear about my latest posts. Just click the button below!
Comments