Full routing analysis

Last time, we looked at the route from the ListOrders scene to the ShowOrder scene in details. This time, we’ll examine all the routes from all the scenes in the CleanStore app, so that you can see how the new and improved router handle other situations.

Routing in Clean Swift is a simple 3-step process:

  1. Getting a hold on the destination
  2. Passing data to the destination
  3. Navigating to the destination

Every route goes through these 3 steps. Step 2 is optional if you don’t have any data to pass. Step 3 is also optional if you use storyboard segue. So routing can be even simpler than this 3-step process.

In other words, if your route is based on storyboard segue and you don’t need to pass data, you can skip steps 2 and 3. If you skip them, that means you don’t need the destination references, and you can skip step 1 too. You don’t have to do anything!

When we look at each route using this 1-2-3 process below, even when some of these steps are optional, I still choose to implement them, so you can see the full picture.

Routes from the ListOrders scene

From the ListOrders scene, the user can tap the Add button to create a new order. The user can also tap an order row to view the order details. So the ListOrdersRoutingLogic protocol declares these two routeTo methods:

Create a new order

Step 1. Getting a hold on the destination (routeToCreateOrder(segue:). You can get the destination view controller from the segue as usual, and the destination data store from the view controller’s router.

Step 2. Passing data to the destination (passDataToCreateOrder(source:destination)). There is no data to be passed, so the method is empty.

Step 3. Navigating to the destination (navigateToCreateOrder(source:destination). Navigation is already taken care of by the segue. However, I implement this method with source.show(destination, sender: nil), which is the default, catch-all way of presenting view controller. When you trigger this route programmatically by invoking routeToCreateOrder(segue:) directly, this will be used to present the destination view controller.

Show an existing order

Step 1. Getting a hold on the destination (routeToShowOrder(segue:). You can get the destination view controller from the segue as usual, and the destination data store from the view controller’s router.

Step 2. Passing data to the destination (passDataToShowOrder(source:destination)). The order of the selected table view row is passed to the destination data store.

Step 3. Navigating to the destination (navigateToShowOrder(source:destination). Navigation is already taken care of by the segue. However, I implement this method with source.show(destination, sender: nil), which is the default, catch-all way of presenting view controller. When you trigger this route programmatically by invoking routeToShowOrder(segue:) directly, this will be used to present the destination view controller.

Routes from the CreateOrder scene

From the CreateOrder scene, the user can tap the Save button to create (if new) or update (if existing) the order, and go back to the previous scene. For a new order, the user goes back to the ListOrders scene, whereas for an existing order, the user goes back to the ShowOrder scene. So the CreateOrderRoutingLogic protocol declares these two routeTo methods:

Save a new order

Step 1. Getting a hold on the destination (routeToListOrders(segue:). Since you already know the source view controller needs to be popped off the navigation controller stack, the destination view controller is simply the view controller beneath the top view controller.

Step 2. Passing data to the destination (passDataToListOrders(source:destination)). There is no data to be passed, so the method is empty.

Step 3. Navigating to the destination (navigateToListOrders(source:destination). After the new order is saved, we pop the top view controller off the navigation controller stack.

Update an existing order

Step 1. Getting a hold on the destination (routeToShowOrder(segue:). Since you already know the source view controller needs to be popped off the navigation controller stack, the destination view controller is simply the view controller beneath the top view controller.

Step 2. Passing data to the destination (passDataToShowOrder(source:destination)). The updated order is passed to the destination data store so that the order details are refreshed.

Step 3. Navigating to the destination (navigateToShowOrder(source:destination). After the new order is saved, we pop the top view controller off the navigation controller stack.

Go back

When the user taps the Back button, the currently visible view controller is dismissed natively by iOS and no data needs to be passed.

Routes from the ShowOrder scene

From the ShowOrder scene, the user can tap the Edit button to update an existing order. So the ShowOrderRoutingLogic protocol declares this routeTo method:

Edit an existing order

Step 1. Getting a hold on the destination (routeToEditOrder(segue:). You can get the destination view controller from the segue as usual, and the destination data store from the view controller’s router.

Step 2. Passing data to the destination (passDataToEditOrder(source:destination)). The order is passed to the destination data store to be edited.

Step 3. Navigating to the destination (navigateToEditOrder(source:destination). Navigation is already taken care of by the segue. However, I implement this method with source.show(destination, sender: nil), which is the default, catch-all way of presenting view controller. When you trigger this route programmatically by invoking routeToEditOrder(segue:) directly, this will be used to present the destination view controller.

Go back

When the user taps the Back button, the currently visible view controller is dismissed natively by iOS and no data needs to be passed.

Repeatable

You probably notice from above that this is kind of boring and repetitive. You’re doing almost the same thing every time.

But that’s the point!

Routing is boring. It navigates to another scene and passes the data it needs. Every route needs to do these two things.

This 3-step routing process gives you a system that you can rely on. It saves your creativity juice for the more interesting thing such as business logic in your interactors and workers.

Flexible

Another benefit of following this 3-step routing process is its flexibility. You have the option to trigger a route in 3 different ways:

  1. Automatic segue
  2. Manual segue
  3. Programmatic

With an automatic segue, you drag from a UIControl to the destination scene. The segue is performed by iOS when the user taps the UIControl. So step 3 is skipped.

With a manual segue, you drag from the view controller in the source scene to the destination scene. And you invoke the perform(for:sender:) method. The segue is also performed by iOS. So step 3 is skipped.

With a programmatic route, you call the router’s routeTo method directly. This allows you the most freedom to do whatever you want. But you have to provide the how’s in steps 3.

This 3-step routing process works for any of these situations. It’s a rare one size fits all.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

9 Comments

  1. Thanks Raymond for awesome explanation & great improvement over the previous method of routing mechanism !!!

    Please suggest if there is a better way to not pass ‘references’ in the following data passing call:
    var destinationDS = destinationVC.router!.dataStore!
    passDataToShowOrder(source: dataStore!, destination: &destinationDS)

    Also suggest & explain if it’s better to keep the below logic in Router or in ViewController(since clicked UI component is in ViewController, the router could be passed the selected Order object, thus avoiding the clicked UI information in Router):
    let selectedRow = viewController?.tableView.indexPathForSelectedRow?.row
    destination.order = source.orders?[selectedRow!]

    Thanks in advance.

    1. Hi Arpit,

      The premise of the new routing is to cleanly separate the two functions: navigation and passing data. Grabbing the references so that you can pass the source and destination objects into the navigateToSomewhere() and passDataToSomewhere() methods, and getting the relevant data through the view controller reference already in the view controller are the tradeoffs you have to make. The new routing system separates the functions into logical steps so that it’s clear what you need to do at each step. It’s this systematic approach that prevents you from doing the quick and dirty hacks.

      Can it still be improved upon? Absolutely. I’ve done a lot of experimentation before this latest template update. But they’re not ready for release. I had to scrap many of my ideas. When it becomes more appropriate, I’ll update the templates again.

  2. Hi, Raymond:

    I do like and learn a lots from your clean swift framework, but I encountered
    a problem and would like to have some kindly suggestion from you if possible.

    I am building a login page, and after login, the user would be redirected to a home view controller (HomeViewController) with 2 container views.
    Now after entering correct email and password, I would like to fetch a corresponding user object and pass to a view controller in one of the container views inside the home view controller, below is the code inside the LoginRouter:

    But I found that when this method was executed, the home view controller had not been initiated yet so the childViewControllers collection was empty, so there would be a fatal error when assigning the destinationVC variable.

    I figured out a way to store the user data inside the UserDefaults and do not pass the data, but I still would like to ask you if there is a way to pass data in this kind of situation (The data receiver is a childviewcontroller of the destination of segue)

    Thanks in advance

    1. Hi Raymon:

      I also figured out another way to do the data transfer, just to make the master view controller that contained the 2 container views a complete VIP cycle, so I will have a complete

      HomeViewController
      HomeInteractor
      HomePresenter
      HomeRouter

      Instead of a single a old school Xcode generated
      HomeViewController: UIViewController

      but of course the HomePresenter was never used.

      Is this approach a little overkill?

    2. Hi Travis,

      Do you have this issue when using a segue? From what you described, it seems like it only happens in the else clause, which is the non-segue case. In your HomeViewController, you then need to make sure you set up your container views when it’s being initialized. That should resolve the issue you have.

  3. Hi Raymond,

    I was having a problem on the destinationVC.router!.dataStore! in the router module. It displays Fatal error: Unexpectedly found nil while unwrapping an Optional value. Is there any way to avoid this error? Thanks!

Leave a Comment

Your email address will not be published. Required fields are marked *