# Clean, Simple and Composable Routing in Swift for iOS Apps - Part 1

# Clean, Simple and Composable Routing for iOS Apps

This is the first part of a [series](https://hashnode.com/series/clean-simple-and-composable-routing-for-ios-apps-ck7vm42k401n4zis1wu4ar2od) of articles about a Clean, Simple and Composable Routing for iOS apps.

One of the hottest topics in the iOS community is view controller orchestration. There are many concerns about it, such as reusability, testability and ability to handle [deeplinks](https://developer.apple.com/documentation/uikit/inter-process_communication/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app)/[Universal Links](https://developer.apple.com/documentation/uikit/inter-process_communication/allowing_apps_and_websites_to_link_to_your_content).

Most of mid to big projects will have dozens of screens and rather complex routing flows between them.

# App Flow

In order to explore some real-life scenarios of a more complex routing mechanism, including managing a Siri Shortcut flow, let's think of a simple sample app that has a *TabBarController* with a *Shop* and *Wishlist* tabs.

From the *Shop* tab, a user can open a *Product* page. From this *Product* page, the user can open another *Product* page, add an item to their *Wishlist*, which will present a *Pop Up* confirmation, or even create a *Siri Shortcut* for that product.

From the *Wishlist* tab, a user can also open a *Product*, with the same flow as described above, and *Login* pages. From the *Login* page, a user can access the *Sign Up* and *Forgot Password* pages. They can also access the *Forgot Password* page from *Sign Up*. Finally, from the *Forgot Password* page, users can reset their password which will present them with a *Pop Up* confirmation.

Phew 😅. I know that this "simple" example is hard to picture, so have a look a the flow drawn below:

![flow.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144085213/9hvCtLZUO.png align="left")

Dem hand-writing skillz tho 😳

Independent of the app's architecture it's clear that there are many repetitive flows in this example, and it'd be best if we could somehow make them [isolated](https://en.wikipedia.org/wiki/Single_responsibility_principle), [reusable](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), [composable](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) and [testable](https://en.wikipedia.org/wiki/Dependency_inversion_principle) (hi there, [SOLID](https://en.wikipedia.org/wiki/SOLID)! 👋). Seems like a lot, doesn't it? But we'll break these concepts down throughout this post.

An ideal approach will allow us to inject a *Route* into a *Controller* or *ViewModel* so we can easily open and/or close certain screens. Also, we should only be able to access routes that are [relevant to that page](https://en.wikipedia.org/wiki/Interface_segregation_principle). Finally, and probably most importantly, **the approach should be easy to support deeplinks/Universal Links.**

![1*idcPrG7IvDrc-5zc_4RooQ.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144164219/Bnb6cXhEw.gif align="left")

This is the app flow in action 😰

# Coordinators

It seems like that these requirements are easily met by [Coordinators](http://khanlou.com/tag/advanced-coordinators/)/[Flow Controllers](http://merowing.info/2016/01/improve-your-ios-architecture-with-flowcontrollers/), right? Yes, and there's [a lot more](https://www.hackingwithswift.com/articles/175/advanced-coordinator-pattern-tutorial-ios)[relevant content](https://www.raywenderlich.com/158-coordinator-tutorial-for-ios-getting-started) about [them](/@pavlepesic/flow-coordination-pattern-5eb60cd220d5).

Amongst the many different approaches explored by the community, I'd split them between in two main ideas: one that uses *one massive AppCoordinator* that handles the whole app's flow in one place, and another that has *multiple coordinators*, perhaps one per screen if necessary, which are chained together.

The first is more convenient since you just pass the same instance around and you have access through it to the whole app's flow. However, this approach can be messy quite easily for bigger flows, as well as leaking too much information to the view models by exposing routes they don't need.

The latter isolates logic better, but requires more management of references, deallocation, and organisation. In our sample app, would you instantiate a *ProductCoordinator* in the *WishlistCoordinator, ShopCoordinator* and in the *ProductCoordinator* itself since it may also present a product, or would you move it to the *AppCoordinator* itself since it's a very used route?

Apps change quickly, flows are updated constantly, [A/B testing](https://en.wikipedia.org/wiki/A/B_testing) is here and it's real. What may begin with one *Product* being shown from the *Wishlist* might end up with it being presented in many places later one. You often don't know that beforehand and need to refactor it later down the track. This has come back to bite me a few times while using the pattern 🤐.

Don't get me wrong, Coordinators are very good and provide the tools to solve most of the problems with routing, and I'm sure they can be used in a way that avoids these issues mentioned above too, but sometimes these tricky situations may cause you to constantly refactor your flow, get a bit lost with managing coordinator references or re-instantiating/re-testing coordinators in multiple coordinator leaves.

# Routers, a cleaner approach

There are a couple of [good articles](/rosberryapps/the-flexible-routing-approach-in-an-ios-app-eb4b05aa7f52) and [samples](https://github.com/freshOS/Router/tree/master/Sources) about [routing](https://clean-swift.com/routing-in-clean-swift/) in iOS. You should read them too! While playing with the Coordinators and Routers ideas, I hadn't found a standard way that provided me enough flexibility and simplicity to achieve all the requirements mentioned before. So I began to work on something 🕵️‍♂️.

Assuming we want to *route* users to other screens, we'd need a couple of *protocols* for such:

![routable.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144313683/ZzpFY4aFP.png align="left")

A *route* should be able to be shown, closed and dismissed. The difference between close and dismiss is that close will apply the same *Transition* received during the routing, while dismiss will forcibly dismiss the screen.

A *Transition* is as simple as the protocol below. It's a way of opening and closing a screen by applying whatever animation the developer provides. For example, [*Modal*](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part1/RoutingExample/Routing/Transitions/ModalTransition.swift), [*Push*](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part1/RoutingExample/Routing/Transitions/PushTransition.swift) or [*Fade*](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part1/RoutingExample/Routing/Animator/FadeAnimatedTransitioning.swift) transitions (have a look at the source-code to see their implementation).

![transition.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144353696/-OA2AkiPu.png align="left")

Combining *Closable*, *Dismissable* and *Routable* with a *Router* protocol, we're ready to implement our first default router. For simpler applications, the *DefaultRouter* will be everything you need to get your routing logic going.

Have a look at the implementation:

![router.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584181100635/SgHVDhPAL.png align="left")

The *DefaultRouter* is initialised with a *rootTransition* which will be used later on to *close* the *root controller*.

The *route* method takes in a controller to be opened, as well as the transition that will be applied to it. **The *route*'s method *transition* argument has nothing to do with the *rootTransition***. In that method the router is just opening up new screens which will have their own routers and transitions.

Finally, the *close* and *dismiss* methods provide the ability to remove the *root* in different ways, as previously explained\_.\_

# Creating Routes

Now that our *DefaultRouter* is ready, we can start creating the app's routes. We'll need [eight of them](https://github.com/CassiusPacheco/iOS-Routing-Example/tree/part1/RoutingExample/Routing/Routes): *ProductRoute, LoginRoute, PopUpRoute, SignUpRoute, ForgottenPasswordRoute, SiriRoute, WishlistTabRoute* and *ShopTabRoute*.

Have a look at the *LoginRoute* as an example:

![login.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144398845/aQmgFu2vw.png align="left")

We create a protocol *LoginRoute* that specifies what this *route* does. **We then extend this protocol and create a default implementation constrained to classes that implement the *Router* protocol**. Finally, we extend *DefaultRouter* to conform to *LoginRouter*.

Since *DefaultRouter* also conforms to *Router* it gets the default *openLogin* implementation for free! **😱**

Now, breaking down the implementation of *openLogin*, we see that a *ModalTransition* is create on line 7 and passed as the *rootTransition* for the newly created *DefaultRouter.* This router is then passed in to the *LoginViewModel*, which will hold a strong reference to it, ensuring it won't get deallocated.

The view model is then injected into a *LoginViewController*, that's assigned to the router as the *root view controller* on line 12. The *root* reference is *weak*, so no [strong reference cycle](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html) is created here. Our *LoginViewController* instance is embedded into a *UINavigationController* that's routed as a modal on line 14.

The code on line 14 is, probably, the most important concept to be understood here: **note that the *route(to: as:)* is being executed by the current router, not the newly created one**, hence you must use the same *Transition* instance defined as the *rootTransition* on line 8 also in the *route(to: as:)* method on line 14.

The flexibility and reusability of this routing system comes from the fact that one router spawns new routers without making any direct connection between themselves besides the *view controllers* and *view models* that keep the routers alive 🤯.

Once a view controller is dismissed/pop/removed from any stack and deallocated, its view model will deallocate and the router attached to it too, freeing up all resources in memory. All of this is managed automatically by the view controllers stack. No extra work for us! 👏

# Using the Routes

Alright, it's time to put these routes to work! Have a look at the view controller and view model implementations below:

![wishlistvc.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144439913/uy3RtMgJ3.png align="left")

A *typealias* named *Routes* is created to easily define which routes our screen can handle. For now, only the *LoginRoute*. Look how simple it is to use it.

In order to support opening the *Product* page from the *Wishlist* we need to create a route for it too. Here's how it looks:

![product.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144489847/hZgkZuqXi.png align="left")

It's just another *protocol extension* that adds the ability to open product pages with a *PushTransition* to the *DefaultRouter*.

Now we just need to update our *Routes typealias* to include *ProductRoute* and we're done. Easy, isn't it? 🎉

![wishlistvc-2.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144455561/M3w0TJyRK.png align="left")

New routes can be easily added to the **Routes typealias**

# Custom Routers

We've seen how easy it is to extend the *Router* protocol to add new routes to it, and to its implementations, such as the *DefaultRouter,* via protocols. All of this without leaking unnecessary information to the view models, thanks to the *Routes typealias,* which restricts the access to routes which are strictly necessary to the view models.

*DefaultRouter* will take us very far, covering most of our necessities, but let's make things a bit more complex and implement a *SiriRouter* to handle *Siri Intents* in the *Product* page.

![sirirouter.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144520921/DJjB0bwTG.png align="left")

It looks like a lot, but it's just the Intent's delegate verbosity. *SiriRoute* doesn't need to do anything besides conforming to the intent delegate, since everything else will be managed by the *IntentsUI framework*. The route protocol must still be created to ensure the routing is abstracted from the view models, making everything testable.

*SiriRouter* inherits from *DefaultRouter* to ensure all the previous logic to open, close and dismiss controllers is kept intact (again, [Open-closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) 😊).

The router then implements all the delegates required by the shortcut button (I've actually hidden a few delegate methods in the image above for brevity, but in the [source code](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part1/RoutingExample/Routing/Routers/SiriRouter.swift) you'll see the full implementation).

Alright, *SiriRouter* is done. Since we'll use it in the *Product* page, we'll have to update the *ProductRoute* to use it.

![product-siri.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144549157/hfyppqzpq.png align="left")

As you can see, the only change in the route was replacing the *DefaultRouter* on line 8 for the *SiriRouter* on line 9 to enable us to take advantage of the intent handling.

Let's update the product screen to include a Siri Intent:

![productscreen.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144570220/vMy2_VQYx.png align="left")

On line 8 we return the *router* as the intent delegate since *SiriRoute* conforms to that protocol, and *SiriRouter implements it.*

Then *siriButtonDelegate*, A.K.A. *SiriRoute*, is assigned as the shortcut button's delegate. Done, everything else will be managed by *SiriRouter* now 😱.

Lean code, reusable routes and clean routers ❤️.

# Bonus: Deeplink Router

Deeplinking is hard. Handling it can become a nightmare quite quickly, especially when using hardcoded view controller orchestration directly in your view controllers.

If you've read everything till here and paid close attention to it (thanks! 🙏), you know what we're up to next. We'll build a *DeeplinkRouter*! 💪

Our routing system is already pretty good, what else do we need in order to open arbitrary links? A way of mapping urls to screens and a way to route users to them. So, I present you… *Deeplinkable*! I know, I'm really bad at naming things 🙈.

![deeplinkable.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144594842/AlxoeMeK6.png align="left")

So far our routes implement a single way of *transitioning* to the screens, but when deeplinking to a screen the strategy may change. A screen that we used to *push* may now be required by stakeholders to be presented as a *modal*, for example.

That's what *route(to url: as transition:)* aims to solve. Differently to the other routing methods, this one returns a boolean because some URLs may not be supported yet, and we might want to consider them separately.

Before beginning to work on *DeeplinkRouter* we need to update our routes to handle opening with different transitions. Thanks to *protocol extensions* power, we can easily do that by following this approach:

![product route 3.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144642074/zk04W2k9d.png align="left")

Exposing a method that's only accessible to instances that conform to *Router* is a nice and easy way to make the routes more flexible to different transitions.

Since we're following the [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle) and hiding the implementation behind the route's protocol, only *openProduct* will be visible to the view models 💃.

Once all routes are updated to this new pattern, we're ready to create the *DeeplinkRouter.*

![deeplinkrouter.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584181231558/bkJ4_f1rP.png align="left")

There are many ways of mapping your screens to the links relevant to your app. In this example I'm using an enum and relying on the URL's path to match it to an unexistent-made-up website. Use your discretion to parse it in a way that makes sense to your business rules.

Once the paths have been parsed, we make use of the newly created route methods that take a *Transition* as an argument to open the screens. Note that since *Wishlist* and *Shop* are tabs embedded in the *tabBarController* we're just updating their *selectedIndex* via their respective routes. 👌

> This is a simple approach that works fine for this sample app, but bear in mind that in real apps some of these transitions may conflict when previously presented screen were already loaded during the deeplink, etc. It's a good ideia to write some UI Tests for it!

Alright, now is time to update the *SceneDelegate* or *AppDelegate* to instantiate the *TabBarController* and its tabs. However, how can we create tabs using this routing pattern?

It's simple. We should think of our routes as ways to instantiate and present the controllers in our app. In this case, we can't present a tab, so we should create a route that just instantiates it to be used by the *TabBarController*.

![WishlistTabRoute.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584180889498/ZKnZZdIGT.png align="left")

> Optionally, you can also create a TabBarRoute, but we won't do that in this sample.

We now apply the same concept above to the *ShopTabRoute* and create a *MainTabBarController* that accepts a list of controllers as arguments:

![tabcontroller.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584180929471/RPT2caDZJ.png align="left")

Putting everything together, our *SceneDelegate* instantiation will look like this:

![scenedelegate.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584180946436/ZvWRWq-md.png align="left")

Finally, we can add the deeplink methods to handle the links. In the example bellow I've done it for a [custom url scheme](https://developer.apple.com/documentation/uikit/inter-process_communication/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app).

![scenedelegate-deeplink.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584180993249/3nspMdzV-F.png align="left")

Lines 18 and 24 will trigger the *deeplink* helper method on line 28. In this example, I've chosen to always show any deeplink as a *modal*, as you can see on line 30.

I've created a helper method named [*topMostViewController*](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part1/RoutingExample/Utils/UIViewController.swift) that ensures the deeplink will be presented on top of everything else. Note how our *DeeplinkRouter* having this *topMostViewController* as its *root* is able to open the deeplink without issues.

It just works™ 😂.

Finally, we need to keep an instance of the *DeeplinkRouter* in the scene to ensure it's kept alive while the transition happens. It's not being cleaned up here since *sceneDelegate* will never be deallocated. I personally don't think this will be much of an issue since it's just one small *router* object, but if you want to make sure everything is freed up, you can add a completion handler in the *route* method, for example.

![deeplink.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1584144727099/CuCALZ6ti.gif align="left")

# Conclusion, finally

It's been a long read! Thanks for sticking around till the end 🤗.

I find this routing system very dynamic, reusable, easily extensible and testable. There may be a couple of edge cases here and there but overall it's pretty solid.

One very important point I didn't touch in this article is that the routes themselves aren't very testable in the article's example, since we're instantiating them directly with hardcoded values. In a real app they'll have many dependencies and be even more complex to instantiate.

Don't panic! This routing system goes along very well with [Dependency Container](https://github.com/CassiusPacheco/DependencyContainer) frameworks or even factories, making it more testable. I intend to write more about it in the future, stay tuned! 😊

The [source-code of the project is available on my Github](https://github.com/CassiusPacheco/iOS-Routing-Example/tree/part1), with all the implementations done.

Continue reading this series by having a look at the [part 2 here](https://cassiuspacheco.com/applying-dependency-injection-to-composable-routing-in-swift-for-ios-apps-part-2-ck7v10xaz01h9zis1oksoe7h0).

Thanks for reading and let me know what you think. Feel free to message me up on [Twitter](http://twitter.com/cassiuspacheco) too!
