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 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.
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.
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.
The Routing library has no dependency on any outside code, making it perfect for modularized code bases.
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.