Going deeper into the RouteComposer configuration

Eugene Kazaev
ITNEXT
Published in
4 min readSep 4, 2018

--

In my previous article I brought you through RouteComposer— the library we use in our iOS applications that handles composition, navigation and deep-linking tasks. In this article I will go over some tricky elements of the library.

How the Router parses the configuration:

The Router goes by the chain of steps starting from the first step. If the Finder of this step says that the UIViewController of this step does not exist, it moves on to the next one. It does this until one of the finders say that it has found the UIViewController described in the chain. Then Router starts to move backwards through the chain of steps and starts to create the UIViewController for each step using its Factory and integrates it into the stack using the Action attached to this step.

It is easier to think about how to configure your routing to the UIViewController if you start to think that the user can be anywhere in the application and has received an universal link that requests your application to show a particular UIViewController.

StackIteratingFinder options:

Behaviour of the StackIteratingFinder can be changed using the SearchOptions. They are:

  • current: The topmost view controller
  • visible: If the view controller is a container, search in its visible view controllers (Example: UINavigationController always has one visible view controller, UISplitController which can han have 2 visible controllers if expanded.)
  • containing: If the view controller is a container, search in all the view controllers it contains (i.e. All the view controllers in the UINavigationController before the one that is currently visible)
  • presenting: Search in all the view controllers that are under the topmost one
  • presented: Search from the view controller provided in all the view controllers that it are presented (It does not make sense to use it with the StackIteratingFinder as it always starts from the topmost view controller so there won't be any view controllers above)

The next image may help you to imagine SearchOptions in the Example app:

If you want it so that the StackIteratingFinder would look for the desired view controller everywhere, but only if they are visible, it should be set like the following:

ClassFinder<CircleViewController, Any?>(options: [.current, .visible, .presenting])

Configuration Q&A:

I have some UIViewController as a root and I want it to be replaced with the HomeViewController:

The XibFactory will load the HomeViewController from the xib file named HomeViewController.xib

Do not forget that if you use a combination of abstract Finder and Factory - you must specify the types of UIViewController and Context for one of them ClassFinder<HomeViewController, Any?>

What will happen if, in the configuration above, I will replace RootViewControllerStep with CurrentViewControllerStep?

It will work if the user is not in some UIViewController that is presented modally. If they are, ReplaceRoot can not replace the modally presented UIViewController and the navigation will fail. If you want this configuration to work in all cases - you should explain to the router that it should start building the stack from the root view controller. Then the router will dismiss all the modal view controllers above the root view controller if there are any.

I want to push the AccountViewController into any UINavigationController that is present anywhere on the screen (even if the UINavigationController is under some modal UIViewController):

Why is the NilFactory used here? That means: look for the UINavigationController everywhere, but do not create it if it is not found (the routing will fail in this case). The NilFactory usually has to be accompanied by the NilAction as if the factory did not create anything. It should not be integrated in to the stack

The UIViewController should be pushed into any UINavigationController if it is present on the screen, if not - presented modally:

I want to present the UITabBarController with the HomeViewController and the AccountViewController in the tabs:

I want to use custom UIViewControllerTransitioningDelegate with the PresentModally action:

I want to navigate to the AccountViewController if the user is in another tab or even if the user is in some UIViewController presented modally:

Why is the NilFactory used here? We do not need to build anything: The AccountViewController will be built in the dependent tabScreen configuration. See the tabScreen configuration above.

I want to modally present ForgotPasswordViewController, but after LoginViewController in the UINavigationController:

With the configuration above you will be able to navigate to both screens using the Router

What will happen if, in the configuration above, I will replace the CurrentViewControllerStep with the RootViewControllerStep?

It will work, but it means that the router has to start building the stack from the root UIViewController, so if the user is in some UIViewController presented modally — the router will close it before it will start the navigation.

The app has a tab bar controller with the HomeViewController and the BagViewController inside. The user should be able to navigate to the bag manually using the tab bar. But if they tap on the button "Go to Bag" in the HomeViewController, the app should present the BagViewController modally. The same should happen if the user has to be sent to the bag using an Universal Link.

There are two ways of implementing this configuration:

  1. To use the NilFinder which means that the router will never find an existing BagViewController on the screen and will always create a new one and present it modally. However, this has a downside: If the user is already in the BagViewController presented modally, and then he taps on the push notification that deep links him to the bag again, the router will build another BagViewController and present it modally on top.
  2. Tweak the ClassFinder slightly so it will ignore the BagViewController that is not presented modally and use it in the configuration:

I will be happy with your comments and suggestions.

PS: If you like the library, do not forget to give it a star on GitHub!

--

--