All About Routing in Clean Swift

Since I started talking about Clean Swift iOS architecture, I’ve received a lot of positive feedback. Many people have downloaded my Xcode templates and put them to good use. I am truly grateful that I am able to help you write better apps.

Many of you also asked some very insightful questions. I’ve answered some but not all. One question jumps out to me is routing and data passing.

In my original post on Clean Swift, I only showed the CreateOrder scene. And then I wrote a few posts about TDD that adds a ListOrders scene as the initial scene. When the user taps the + button in the navigation bar, it routes to the CreateOrder scene.

But that’s only a very simple segue. It’s not enough to show you exactly how routing should work. Especially how passing data should be done.

In this post, I’ll add a ShowOrder scene. When the user selects an order in the table view in the ListOrders scene, it’ll transition to the ShowOrder scene to show the order details.

To focus on the topic of routing and data passing, I’ll only show the code relevant to these concepts. You can find the full implementation of the ShowOrder scene on GitHub. You can also read about how to create and implement a scene in my original post on Clean Swift.

Are you currently doing this in your view controller?

In typical massive view controller fashion, there are 2 ways you can transition to the next scene:

  • Set up a segue in the storyboard using drag-n-drop
  • Invoke UIViewController’s presentViewController(_:animated:completion) method programmatically

You then pass data to the next scene inside the prepareForSegue(_:sender) method, like so:

All these tasks are handled by your view controller, contributing to its massiveness.

Let’s look at how Clean Swift handle routing next.

How does routing work in Clean Swift?

The Configurator in Clean Swift hides away the setup details from you. You don’t even need to open the file 99% of the time. But it’s useful to take a look at it in order to understand routing. I promise this’ll be short.

In ListOrdersConfigurator.swift:

First, the ListOrdersViewController extension overrides the prepareForSegue(_:sender) method. Your view controller doesn’t have to implement it! This means we’re already extracting the routing logic out of the view controller.

Remember the router variable in the view controller? Inside the prepareForSegue(_:sender) method, it simply invokes the router’s passDataToNextScene(_:) method, which you’ll look at in a moment.

Next, the ListOrdersConfigurator’s configure(:_) method creates the ListOrdersRouter instance. It then makes the view controller point to the router. and vice versa.

Doesn’t this cause a circular dependency problem and eventually lead to running out of memory? Not if the router’s view controller is a weak variable.

Let’s take a look at ListOrdersRouter.swift:

In ListOrdersRouter, the viewController variable is a weak variable.

I mentioned at the beginning that there are two ways you can transition to the next scene – storyboard segue and programmatically presenting the next view controller.

Whenever you can use a storyboard segue, you should. That’s because it helps greatly to visualize your app’s intended navigation with the arrows representing the segue pointing from the source scene to the destination scene.

With a storyboard segue, the prepareForSegue(_:sender) method is called by iOS. And the ListOrdersViewController extension already overrides this method. It simply invokes the passDataToNextScene(_:) method. So all you really need to do is to match segue.identifier to your segue identifier (i.e. ShowShowOrderScene), call the corresponding passDataToShowOrderScene(_:) method, and do your data passing in that method.

To pass a selected order from the ListOrders scene to the ShowOrder scene, you first find out the indexPath for the selected row in the table view. And then find the order in the orders array. Finally, set the order to the selected order. Pretty standard stuff here. It’s the same thing you’ve done before.

However, and this is important. You now do this in the router, not in the view controller!

The simple trick is to just override the prepareForSegue(_:sender) method and invoke the passDataToNextScene(_:) method on the router. And this detail is ‘hidden’ away from you by the Configurator.

What about when you have to programmatically present the next view controller?

Before, you have to call self.presentViewController(_:animated:completion) in your view controller. How do we move that out of the view controller?

The ListOrdersRouterInput protocol declares an API for the router so that the view controller can delegate the routing responsibility to the router.

Let’s assume you want to transition to Somewhere. So the protocol declares a navigateToSomewhere() method. The router implements this method and invoke the presentViewController(_:animated:completion) method from within.

In the templates, I listed 4 different ways you can typically present another view controller:

  • Trigger a segue you’ve defined in the storyboard: performSegueWithIdentifier(_:sender)
  • Present another view controller: presentViewController(_:animated:completion)
  • Push a view controller onto the navigation controller stack: pushViewController(_:animated)
  • Push or present a view controller from a different storyboard

The last one is big, especially for projects that need to support iOS 8 and below. With the router, you can easily hide away the details of how to present the next scene on the screen from the view controller.

Your view controller simply asks the router to navigate. And your router only needs to present.

Segue? Present? Push? Even an entirely different storyboard? Your view controller need not to know. Your router handles those details.

And, if there are multiple code paths in your view controller that need to navigate to the same scene, these navigateToSomewhere(), navigateToSomeOtherPlace(), navigateToWhereverYouWant() methods are reusable.

Passing data between scenes

Let’s look at the ListOrdersRouter’s passDataToShowOrderScene(_:) method again:

It’s reasonable to ask the view controller for the selected row in the table view because it is managed by the view controller.

However, it calls the view controller’s output, an interactor, to get the selected order and set the order to be shown.

Calling viewController.output.orders to get the data in the source scene and showOrderViewController.output.order to set the data in the destination scene is not ideal. It doesn’t read all that great.

I am experimenting with some improvement changes for the router. For now, just keep these in mind:

  • The output variable of the view controller is simply the interactor.
  • Your business logic, including non-persistent data, is best stored in the interactor.
  • Your domain model (i.e. Order) shouldn’t spill outside the interactor.
  • The presenter formats an Order domain model into a DisplayedOrder view model.
  • The view controller doesn’t directly manipulate your domain model.
  • It’s ok to pass a domain model such as an order from one interactor to another interactor.

The last bullet in this list can be better understood visually when you consider how scenes are connected.

Routing

The ListOrders and ShowOrder scenes have their own VIP cycles respectively. The view controller has a reference to a router. The ListOrdersRouter connects to the ShowOrderViewController of the next scene.

At the view controller level, your display logic manipulates view models such as DisplayedOrder. At the interactor level, your business logic manipulates domain models such as Order. At the presenter level, your presentation logic formats domain models into view models.

As I mentioned, I am making improvements to the router. Make sure you subscribe to my list below. When you download the Xcode templates from Gumroad and provide your email, you’ll receive an email when I update the templates.

Get the Clean Swift Xcode Templates

Subscribe below to get my Xcode templates and learn how to apply the VIP cycle to your projects, extract business and presentation logic into interactor and presenter, navigate to different scenes using multiple storyboards, and write fast, maintainable tests with confidence to make changes.

I promise I'll never send you spam. You can unsubscribe at any time.

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.

47 Comments

  1. Ok lets say i have a TableView on a VC.
    Originally i had put my data array in the VC class.

    VC -> tells interactor (giveMeMoreData) -> interactor gets data and send to presenter -> Presenter formats the data and sends it to VC. -> VC Stores formatted data in array

    So whenever the TableView needs information to be displayed on a tableView, it simply gets the info from the already formatted data in the array.

    However, You have said above that –> Your business logic, including non-persistent data, is best stored in the interactor. Like you did above:

    So if i moved the array from the VC to the Interactor, i was wondering how it will get the data formatted back to the tableView?

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = ……….
    cell.label.text = output.data[indexPath.row] // Unformatted data
    return cell
    }

    But output.data[indexPath.row] is unformatted data right? It has not gone through the Presenter yet. I was confused on this.

    1. Hi Jay,

      There is a difference between business logic and view model. Let me explain in terms of the CleanStore example app.

      Your selectedOrder is business logic. One requirement for the app is to allow an user to select an order to view its details. So when the user selects an order, you want to store a reference to it in your interactor. You can then proceed by fetching/getting the details of this selected order, format it for display, …etc to complete the VIP cycle. Finally, your business requirement specifies that the order details should be displayed in a new view, so your router navigates to the Show Order scene which displays the order details.

      Your formattedOrders is not business logic. It is simply one representation of an order. It is produced by the presenter for the sole purpose of displaying them in the List Orders scene by your view controller. A different List My Orders scene can have a different formattedOrders to display different attributes of an order. For example, it’s not necessary to display the person’s name because they all belong to the same user. That would just be redundant. That’s why I choose to define FormattedOrder inside the view model struct. A different view model can have a different FormattedOrder. Theoretically, your Order model can be cross platform and device. It should still work if you display the orders on an Android device or in the browser. (Of course, there is the language difference that makes this more tricky, but you can certainly implement your business logic in pure C to achieve this.) These view models are not business logic. They exist only to be displayed. If you have a non-GUI, command-line application, when you type select order -id 123, it simply returns ‘OK’. Then you wouldn’t need a view model at all, but you still have a selectedOrder because the business requirement hasn’t changed.

      That’s a really long answer to your question, but I feel it’s very important to be clear about what business logic is and isn’t. Regardless if you use Clean Swift or not, or what type of application you write, I hope it serves you well.

  2. Hi Raymond. This is an awesome tutorial. I have just recently stumbled upon VIPER and am very interested to use this architecture in my current project.

    I have a question.

    A viewcontroller(vc1) in one of my scene has to open UIImagePickerController and after picking an image it should route to another viewcontroller(vc2) with the picked image as vc2’s dependency.

    My dilemma is that UIImagePickerController obviously does not have its associated “IPER” classes and apple suggests not to subclass UIImagePickerController.

    So the question is how do I use VIPER in this scenario?

    Does the router of vc1 open the UImagePickerController by treating it as just another scene? Or vc1 itself can open the picker in traditional way, dismiss it and then use the router to segue to vc2 while passing image to it.

    1. Hi Saugat,

      You’re right that the UIImagePickerController does not fall into the VIP cycle in that there’s not interactor and presenter. What I would do is to present the UIImagePickerController in Scene1Interactor. You want to set the delegate to be the interactor. When you get an image, you can process it in the interactor.

      1. Hi Raymond,

        Would you really put some UI related code inside of an Interactor? Shouldn’t the Interactor be UI independent (not even have UIKit framework imported)?
        Would the solution be better if the ViewController conforms to UIImagePickerControllerDelegate. The ViewController would implement those methods by calling the Interactor to do some action?

        1. Hi Kevin,

          If I do put UIImagePickerController stuff in the interactor, I’m treating the whole taking/choosing a photo flow as a self contained business use case. Scene1ViewController should only have to deal with the UI for scene 1. After the image picker UI is presented, it’s not scene 1 anymore. But since we don’t have a VIP cycle for it, I’m fine with leaving it in the interactor. When it becomes necessary, I can easily move it out of the interactor and into its own ImagePickerWorker.

          I used to prefer importing only Foundation in the interactor too. But I’ve grown to love some flexibility there as long as it’s justifiable. Every app and situation is different.

          1. Thanks for your answer!
            I like the idea of an ImagePickerWorker.

            I used VIPER in a project and I struggled sometimes to do things like this, that can get tricky if you oblige yourself to keep UIKit in the UIViewController only. I think we need to be pragmatic.

  3. Hello Raymond,

    The only problem I see here is that you are coupling view data with business data when you use the selectedIndexPath as the identifier of the object.

    What if I decide via the presenter, to order them differently? Then I will have to change the order I store them in my interactor as well.

    I am saying this because I haven’t found the ideal way to decouple this stuff, as the only one who knows how to convert business model to view model is the Presenter.

    How would you handle this?

    Also I am working in a new approach with the router as well to avoid the output thing.

    It would be awesome if you could take a look at it when I finish!

    1. Hi Miguel,

      Let’s walk through your example. Assume you are using a segmented control for sorting the orders such as by order date and order status. When the user taps the segmented control to change the sort order, the IBAction in your view controller is invoked. You then ask the interactor to sort the orders. Since it’s just a sort, there’s no re-fetching. The interactor just needs to sort the orders array. The interactor then passes the sorted orders to the presenter. The presenter builds the view models using the sorted orders and tells the view controller to refresh the table view. The indexes are updated. The orders and the view models are always in sync. I don’t see any problem.

      The selectedIndexPath is an index in the table view, but it becomes a business logic once it’s passed to the interactor. I think you’re overthinking a little bit here.

      1. OK, then you aproach it by having the same order in the Interactor.

        I guess its not that bad now that you made me think.

        Did you had some time to look at the routing alternative?

        Thank you!

  4. Hi Raymond, I’ve read the blog and I’m applying VIP on my project.

    There are, however, some things I do not understand completely.
    As you said, the business logic and the view logic are different things, because of that you store the unformatted orders on the Interactor, but I have a doubt regarding this.

    In my project I have a list of images (json that contains several data as the name of the image, the user, and so on), which I download over the internet and later, I download the images from the links, and I show them on the list.

    I want to show the image in detail when I tap on a cell, so it’ll load another controller.

    What I don’t really understand is how to store the images (link or UIImage) so that I can pass them when required in the router. What I want to do is show the low quality image (from the list) while the high quality image is being downloaded, so I’m wondering where and how to store both low quality image and the images’ data.

    1. Hi Fco,

      After you retrieve the list of images as JSON, you first want to create the domain models. For example, you can have a WebImage struct that looks like:

      You’ll return this [WebImage] array from your API class to the interactor. Your interactor will want to keep a reference to this array as it’ll need it later for routing. It then passes these in the response model to the presenter. The presenter will then construct a view model to pass further along to the view controller. The contents of your view model depends on what exactly you need to display. For example, it’ll have the thumbnailURL but not the originalURL because in the list view, you only need to show the low quality images. In your view controller, you’ll then load the images when displaying the cells.

      When the user taps on a cell, you’ll want to find the WebImage in your interactor’s [WebImage] and pass that along to the next details scene. There is example code for doing this in my Xcode templates. When viewDidLoad() is invoked, you want to fetch the high quality image this time using the originalURL and display that in your image view.

      Does that make sense?

  5. Hi Raymond
    Thanks for your job is excellent.
    I have some doubts about the router class, when I use the push view controller not the segue I made the following. I created a method in the router class like this:
    func navigateToAddItemScreen(){
    let addItemScreen = viewController.storyboard?.instantiateViewControllerWithIdentifier(“AddItemViewController”)
    viewController.navigationController?.pushViewController(addItemScreen!, animated: true)
    }

    and in the controller I call it of the following way:

    router.navigateToAddItemScreen()

    Is this behavior correct?. I am asking because I am not using the protocol ItemListRouterInput and I think I should use it but I don’t know how?
    Can you help me with that?

    1. Hi César,

      You’re doing it right. The ItemListRouterInput is simply a protocol to explicitly declare all the routes the router can navigate to. In the view controller, the template defines var router: ItemListRouter!. But you can just easily change it to var router: ItemListRouterInput!.

  6. Hi Raymond,
    About the improvement of the Router, recently I also encountered the passing data problem, and finally I came up with a solution. I would like to share my approach here and hear out your opinions:

    Back to the old days, we only have 1 massive viewController. When we pass data to another scene, we just pass from the source viewController to the next ViewController, so it is like 1 -> 1.

    Now we have VIP, 3 components to separate the massive viewController for the sake of separation concern. In the original Clean Swift design, when we need to pass data, the Router still can pass data from the source viewController to the next ViewController. But the ViewController is no longer the old massive viewController, instead it only represents the 1/3 portion of our VIP cycle. so it is like 1/3 -> 1/3. That is why we feel insufficient and always trying to get data from the source Interactor and pass it to the next scene’s Interactor. I think this is the root cause of the problem.

    So, my solution is use Router to combine VIP as 1 massive viewController again, but just for the purpose of passing data.

    How do we achieve this? It’s very simple, basically we just need add another 2 weak reference to the Router and that’s it:

    class TasksRouter {
    weak var viewController: TasksViewController!
    weak var tasksInteractor: TasksInteractor!
    weak var tasksPresenter: TasksPresenter!
    }

    And connect them in the Configurator:
    func configure(viewController: TasksViewController) {
    let router = TasksRouter()
    router.viewController = viewController
    router.tasksInteractor = interactor
    router.tasksPresenter = presenter
    viewController.router = router
    }

    Example:
    When user select a row, in the viewController, we can call a Router’s method passing the indexPath.row:

    router.goToDetailView(row: indexPath.row)

    And in the Router, we can get the data from our Interactor and pass in directly to the next scene’s Interactor:

    class TasksRouter {
    weak var viewController: TasksViewController!
    weak var tasksInteractor: TasksInteractor!
    weak var tasksPresenter: TasksPresenter!

    func goToDetailView(row: Int) {
    let storyboard = UIStoryboard(name: “Main”, bundle: nil)
    let nextView = storyboard.instantiateViewControllerWithIdentifier(“DetailView”) as! DetailViewController
    let selectedData = tasksInteractor.datas[row]
    nextView.router.detailViewInteractor.selectedData = selectedData
    viewController.navigationController?.pushViewController(nextView, animated: true)
    }
    }

    Maybe there is a coupling concern, but in my opinion we still have the clean VIP cycle for unit testing, we just glue them together in Router in order to pass data to the next VIP cycle. And we don’t need to unit test Router, right?

    Any suggestions?

    1. Hi Andres,

      It’s great that you guys are already thinking about the data passing problem. While your approach certainly ‘works,’ it does some with some sacrifices as you pointed them out already.

      All I can say at this point is just stay tuned. You’ll definitely hear from me on his topic.

  7. Hi Raymond,

    I really appreciate your work here, it is highly useful for me and my project. However, I still have some troubles to bring the ideas to life, so my question is how to handle the following use case:

    Given a view controller according to the Clean Swift architecture. It shows a table with different types of cells, among others, with a date cell. The cell content is presented as string, while the model has an NSDate.

    Now, a tap on the table cell shall provide a Date Picker component (some 3rd party cocoapods stuff), i.e.: tap -> show date picker -> update table cell if confirmed, leave it if cancelled.

    Currently, the view controller triggers the router to show the date picker controller. But: How do I get the current value (the NSDate, not the string which is available in the view controller) to the date picker? Should there be a Worker handling the entered date (e.g., validating against date ranges)? How should the whole workflow look like?

    Questions over questions… 😉

    It would be great if you could give me some hints, I believe this may address a common problem for other newbies like me…

    Best regards,
    Hardy

    1. Hi Hardy,

      Indeed, your scenario is very common. The currently selected NSDate is state information. I would save that in the interactor with a variable such as var selectedDate: NSDate.

      When you show the date picker in a new scene, you can pass the selectedDate to your date picker such as datePickerViewController.interactor.selectedDate = viewController.interactor.selectedDate.

      It may not be immediately obvious why you want to put states in your interactors. But what if you later want to validate the date to make sure the user isn’t picking a date in the past? Business logic is easier to write if you already have all pertinent information right there in the interactor.

      Hope that helps!

      1. Thanks a lot, Raymond, this makes things clearer.
        I still have difficulties to understand the flow between the players. There are two understandings for me:
        (1) ViewController (main) -> Router -> ViewController (picker)
        (2) ViewController (main) -> [Request] -> Interactor -> ViewController (picker) -> [Response] -> Presenter -> [ViewModel] -> ViewController (main)

        Where (1) led originally to my initial question, and (2) is now what I assume to be correct flow according to your response, is it?

        1. Hi Hardy,

          The flow of control is always from V to I to P in the same scene. When you want to go from scene A to scene B, in router A, you can grab the selectedDate from interactor A and pass it to interactor B before calling presentViewController() or performSegue().

  8. Hi Raymond and everyone,

    I have tried to do some navigation, it works pretty awesome and so on 😉 but unfortunately I have also tried to create unwind segue which (in my opinion) is barely impossible to create (I mean in storyboard).

    Do you have some advices? I can create segue back, not unwind, but there is unwind to do so and I would like to use it 🙂

    1. Hi Bart,

      I have used unwind segues before. But, at the time, it seemed it was Apple’s attempt to force a parallelism to forward segues. More so than a well thought out storyboard feature. I rarely use it, but will certainly keep my eyes open if it is improved in future Xcode versions.

      1. Hi Raymond:

        I have an example project with scene1 is a list of items, if I click one of the items, it would lead to a editing page, and when I finish and click the save button, it would lead back to the scene1 and show the updated result.

        I have 2 questions:
        1. It is quite easy to use unwind segue to manage such scenario in traditional iOS design pattern, but I found that I could not create a unwind segue with clean swift framework, why is that?
        2. You mention about avoid unwind segue, so would you please give me some hints about how to implement my use case without unwind segue?

        Thanks

        1. Hi Raymond:

          After searching “stackoverflow”, I found that the extension of the viewcontroller in the configurator file would interfere with the interpretation of the unwind segue by Xcode.
          After commenting out the extension, my unwind segue appeared and I could link the button to it.

          1. Hi Travis,

            Yes, this is a known problem with storyboards. It doesn’t recognize the view controller class names due to the order in which things are setup. I’ll look for a way to fix this in the next update. But unwind segues in themselves should work. Your temporary fix should do it for now.

  9. Hi Raymond,

    I got a question about the view and DM.
    In my scene, the DM are bunch of stores which has coordinate,name and other staff. And I used a MKMapView to show every store, the store annotationView shows different image according to store state, when tap on annotationView, a callout View shows, then tap on the calloutView, the ListStoreScene will pass selected DomainModel of store to next scene.
    Because MKAnnotation dont have a index, it seems like the only way passing data to next scene is to bind the DM with annotation. Then in Router, I can use viewController.mapView.selectedAnnotations.first to get DM and pass to next scene. I konw its not right, could you help me.

    1. Hi Johnny,

      I don’t think using let selectedStore = viewController.mapView.selectedAnnotations.first is wrong. You only have one store selected at any time, and that gives you the selected store in one line in your router.

      You could have some sort of a StoreManager to keep the list of all stores, their states, and what not. You can then have a selectedStore() method to hide the details of how you get the selected store. But I think that is over-engineered until you reach the point where all this is necessary.

  10. Hi Raymond!

    I have a question about data consistency. I haven’t noticed any information about it in VIP architecture. For example, on the “List” screen there are list of posts, which can have associated comments, and there is a comments counter for each post in cells. Pretty common UI. User can tap on cell to navigate to post details screen, and there is some text input. So, if user adds new comment on “Details” screen, then on “List” screen the comments counter in cell should update the value. How can VIP handle such behavior? Should those two screens share one Interactor to display one set of data?

    Thanks

  11. Hi Yury,

    Good question.

    First, don’t share the same interactor for multiple scenes. You can, however, share workers for multiple scenes. But that’s a different topic.

    Back to you question. I can see two basic approaches:

    1. Use delegation for the Details scene to tell the List scene the comment count should be incremented.
    2. Upon returning to the Details scene, possibly in viewWillAppear(), you retrieve the new comment count by other means – database, network, and whatnot.
  12. Hi!

    “router connects scenes by view controllers”
    but actually it connects scenes by view controllers outputs – code in passDataToShowWhatever boils down to
    destinationController.output.xxx = sourceController.output.yyy

    Which translates to interactor interactor relation (shielded by boundary controller output protocols).

    Now, it would seem it forces some reverse-use of the VIP cycle protocols. I mean – we are setting data needed for (target) controller using its output (implicitly using (target) interactor input protocol). In the same way we are implicitly using (source) interactor input protocol to get data from (source) controller.

    This introduces some confusion as we are reversing meaning of output/input protocols plus it pollutes output protocols with extra methods/variables to access routing data plus it is a bit informal in comparison to well-defined data in VIP cycle (request/response/model).

    One idea to clean this might be to create separate protocol(s) detached from VIP cycle which will be defining how to get/set data while routing (i.e. RoutingData).

    // router
    let sourceData:Scene0RoutingDataProvider = sourceController.routingDataProvider
    let targetData:Scene1RoutingDataProvider = targetController.routingDataProvider

    targetData.whatever = sourceData.something

    // controller
    var routingDataProvidera:Scene0RoutingDataProvider!

    // interactor
    class Scene0Interactor: Scene0InteractorInput, Scene0RoutingDataProvider

    // configurator
    viewController.routingDataProvider = interactor

    BR,

    1. Hi topeerz,

      Thanks for your comment. And yes, the router, as it is currently, isn’t perfect. The data passing requires referencing the output protocols (the interactors). That’s why the router will be completely overhauled in the next update. Stay tuned 🙂

  13. Why doesn’t the router class confirm to the ListOrdersRouterInput protocol? And shouldnt the viewcontroller have an object of ListOrdersRouterInput rather than ListOrdersRouter? Why does it have an object of the concrete class?

  14. Hi Raymond, great site.
    I have a question about routing. You say on this page

    “I mentioned at the beginning that there are two ways you can transition to the next scene – storyboard segue and programmatically presenting the next view controller.”

    Which technically is true if your app starts as a single View app, but if like my app, it uses a tab bar that needs to share data then this isn’t correct. There are more than two.

    I had to create a second router (for the second tab) in the first tab’s view controller, create properties in that router and set the values for the data I needed to carry across to tab 2. Then in tab 2’s view controller on load, create a method that uses the TabBarController delegate to get Tab 1’s ViewController, which gave me the populated router and subsequently swapped tab 2’s router for the populated one.

    This felt very hacky indeed, and worse I couldn’t descend the protocol chain with the new data, meaning no unit tests or clean code.

    What I would like to ask is, what would the correct approach be to routing data between tab view controllers (that do not use segue)?

    Is there any future architecture plans to make lets say a Global Store? or possibly extend the routers to talk to each other?

    1. Hi gslondon,

      With the scenario you raised, I would first think hard about which approach is more suitable. First, you can pass data from one scene to another. Second, you can have a shard data store.

      With the first approach, I can see a big problem with tab bar controller. A typical app with a tab bar also uses navigation controller for each tab. If you navigate to the 4th controller in the navigation stack in the first tab and then go to the second tab and navigate to the 3rd controller. Now you can go back to the first tab, and so on. There is infinite possibility. You’ll end up with a spider web of all possible paths.

      At that point, it’ll be easier to just create a shared data store that you can refresh from on viewDidAppear(). Even delegation or notification will be a better fit.

      I think passing data in routing is most suited when traversing the navigation stack and morally presenting and dismissing view controllers.

      I don’t know your app specifics. But you want to make sure you are using the right tool for the right situation.

  15. I have introduced a “clean-swift” tag on StackOverflow. I think it would be helpful to ask good questions there. @Raymond: Maybe you can monitor this flag and jump in if needed.

      1. If you like, maybe promote to ask questions suitable for SO on SO. I find it hard (to impossible) to find and follow information here.

  16. Hi Ray, again thanks for these great resources.

    I’m wondering if you would solve this (pretty common) routing case like I did or if there’s a more elegant way. My destination view controller is contained in a navigation controller as I’m using a modal presentation segue (your CleanStore sample only contains push segues). This is why I had to adapt the router methods, for example:

    func routeToAddUser(segue: UIStoryboardSegue?) {
    if let segue = segue {
    let destinationVC = (segue.destination as! UINavigationController).viewControllers[0] as! AddUserViewController
    var destinationDS = destinationVC.router!.dataStore!
    passDataToAddUser(source: dataStore!, destination: &destinationDS)
    }
    else {
    let destinationVC = viewController?.storyboard?.instantiateViewController(withIdentifier: “AddUserViewController”) as! AddUserViewController
    let destinationNC = UINavigationController(rootViewController: destinationVC)
    var destinationDS = destinationVC.router!.dataStore!
    passDataToAddUser(source: dataStore!, destination: &destinationDS)
    navigateToAddUser(source: viewController!, destination: destinationNC)
    }
    }

    func navigateToAddUser(source: ListUsersViewController, destination: UINavigationController) {
    source.show(destination, sender: nil)
    }

    This, of course, reduces readability a bit because you might not immediately see the type of the destination content view controller. But does it make sense?

    1. Hi Nick,

      Having the destination view controller being in a new navigation hierarchy is actually a pretty common case. As you’ve already found out, regardless of what architecture you use or lack thereof, you have to jump through some hoops to get to the destination view controller as in let destinationVC = (segue.destination as! UINavigationController).viewControllers[0] as! AddUserViewController. But that’s okay. All the ugliness is contained in the routeToAddUser(segue:) method in Clean Swift, whereas it’s in the view controller in MVC.

  17. Often a view activity triggers a call in the interactor…which leads to a route to a new view controller. Generally I’m finding the interactor is the place where I want to navigate but am I supposed to make up View and Presenter protocols just to pass an action through to the view controller router class? Seems like a lot of jumping…just to trigger a router/present new view controller call? Any nicer way to allow the interactor to trigger the route to next view? Thx…

Leave a Comment

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