{"id":771,"url":"https://github.com/ekazaev/route-composer","last_synced_at":"2025-04-13T23:42:19.700Z","repository":{"id":43886379,"uuid":"118522173","full_name":"ekazaev/route-composer","owner":"ekazaev","description":"Protocol oriented, Cocoa UI abstractions based library that helps to handle view controllers composition, navigation and deep linking tasks in the iOS application. Can be used as the universal replacement for the Coordinator pattern.","archived":false,"fork":false,"pushed_at":"2024-12-04T14:21:34.000Z","size":65502,"stargazers_count":905,"open_issues_count":4,"forks_count":65,"subscribers_count":33,"default_branch":"master","last_synced_at":"2025-03-30T20:01:42.163Z","etag":null,"topics":["controllers-composition","coordinator","coordinator-pattern","deeplink","deeplinks","factory","finder","ios","mvvm-c","mvvm-coordinator","navigation","router","routing-engine","swift","swift5","universal-links"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ekazaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"ekazaev"}},"created_at":"2018-01-22T22:10:16.000Z","updated_at":"2025-03-25T08:42:38.000Z","dependencies_parsed_at":"2024-12-15T02:00:46.760Z","dependency_job_id":"d18d7482-2ceb-483c-9544-784ca34a6538","html_url":"https://github.com/ekazaev/route-composer","commit_stats":{"total_commits":339,"total_committers":14,"mean_commits":"24.214285714285715","dds":0.5103244837758112,"last_synced_commit":"3907ee1a141bab480cdf38062893dc6732248f03"},"previous_names":[],"tags_count":120,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekazaev%2Froute-composer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekazaev%2Froute-composer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekazaev%2Froute-composer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekazaev%2Froute-composer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ekazaev","download_url":"https://codeload.github.com/ekazaev/route-composer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247550672,"owners_count":20956985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["controllers-composition","coordinator","coordinator-pattern","deeplink","deeplinks","factory","finder","ios","mvvm-c","mvvm-coordinator","navigation","router","routing-engine","swift","swift5","universal-links"],"created_at":"2024-01-05T20:15:30.972Z","updated_at":"2025-04-06T21:03:36.000Z","avatar_url":"https://github.com/ekazaev.png","language":"Swift","funding_links":["https://github.com/sponsors/ekazaev"],"categories":["App Routing","User Interface"],"sub_categories":["Getting Started","Mobile"],"readme":"# RouteComposer\n\n[![CI Status](https://travis-ci.org/ekazaev/route-composer.svg?branch=master\u0026style=flat)](https://travis-ci.org/ekazaev/route-composer)\n[![Release](https://img.shields.io/github/release/ekazaev/route-composer.svg?style=flat\u0026color=darkcyan)](https://github.com/ekazaev/route-composer/releases)\n[![Cocoapods](https://img.shields.io/cocoapods/v/RouteComposer.svg?style=flat)](http://cocoapods.org/pods/RouteComposer)\n[![Swift Package Manager](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg?style=flat)](https://github.com/apple/swift-package-manager)\n[![SwiftUI](https://img.shields.io/badge/SwiftUI-compatible-0AB42A.svg?style=flat)](https://developer.apple.com/xcode/swiftui/)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BA51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Swift 5.9](https://img.shields.io/badge/language-Swift5.9-orange.svg?style=flat)](https://developer.apple.com/swift)\n[![Platform iOS](https://img.shields.io/badge/platform-iOS%209%20—%20iOS%2016-yellow.svg)](https://www.apple.com/ios)\n[![Documentation](https://ekazaev.github.io/route-composer/badge.svg)](https://ekazaev.github.io/route-composer)\n[![Code coverage](https://codecov.io/gh/ekazaev/route-composer/branch/master/graphs/badge.svg?style=flat)](https://ekazaev.github.io/route-composer/tests/index.html)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d54c6461dab64cc5a3be79734588de52)](https://app.codacy.com/gh/ekazaev/route-composer?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=ekazaev/route-composer\u0026utm_campaign=Badge_Grade_Settings)\n[![MIT License](https://img.shields.io/cocoapods/l/RouteComposer.svg?style=flat)](https://github.com/ekazaev/RouteComposer/blob/master/LICENSE)\n[![Twitter](https://img.shields.io/twitter/url/https/github.com/ekazaev/route-composer.svg?style=flat)](https://twitter.com/intent/tweet?text=Check%20it%20out:\u0026url=https%3A%2F%2Fgithub.com%2Fekazaev%2Froute-composer)\n\n`RouteComposer` is the protocol oriented, Cocoa UI abstractions based library that helps to handle view controllers composition, navigation\nand deep linking tasks in the iOS application. \n\nCan be used as the universal replacement for the [Coordinator](https://www.raywenderlich.com/158-coordinator-tutorial-for-ios-getting-started) pattern.\n\n![](https://habrastorage.org/webt/x7/yt/ll/x7ytllwqwgvgxy2rvtmdwj3qkia.png)\n\n## Table of contents\n\n- [Navigation concerns](#navigation-concerns)\n- [Installation](#installation)\n    - [CocoaPods](#cocoapods)\n    - [Swift Package Manager](#swift-package-manager)\n- [Example](#example)\n- [Requirements](#requirements)\n- [Testimonials](#testimonials)\n- [Sponsor this project](#sponsor-this-project)\n- [Usage](#usage)\n    - [Implementation](#implementation)\n        - [Factory](#1-factory)\n        - [Finder](#2-finder)\n        - [Action](#3-action)\n        - [Routing Interceptor](#4-routing-interceptor)\n        - [Context Task](#5-context-task)\n        - [Post Routing Task](#6-post-routing-task)\n    - [Configuring Step](#configuring-step)\n    - [Navigation](#navigation)\n    - [Container View Controllers](#container-view-controllers)\n    - [Deep-linking](#deep-linking)\n    - [Troubleshooting](#troubleshooting)\n- [SwiftUI](#swiftui)\n- [Advanced Configuration](#advanced-configuration)\n- [Contributing](#contributing)\n- [License](#license)\n- [Articles](#articles)\n- [Author](#author)\n\n## Navigation concerns\n\nThere are 2 ways of implementing the navigation available in the iOS application:\n- Built-in mechanism provided by Apple using storyboards and segues\n- Programmatic navigation directly in the code\n\nThe downsides of these two solutions:\n- Built-in mechanism: navigation in the storyboards is relatively static and often requires the extra navigation code in the \n`UIViewController`s and can lead to a lot of boilerplate code\n- Programmatic navigation: forces `UIViewController`s coupling or can be complex depending on the chosen design \npattern (Router, Coordinator) \n\n## RouteComposer helps\n\n- Facilitate the cutting of an application into small logical steps of navigation\n- Provide the navigation configuration in a declarative way and address the majority of the navigation cases\n- Remove navigation code from `UIViewController`s\n- Allow the composition of the `UIViewController`s in different ways according to the application state\n- Make every `UIViewController` deep-linkable out of the box\n- Simplify the creation of the User facing A/B tests with the different navigation and layout patterns\n- Able to work side-by-side with any other navigation mechanism that exist in the IOs application: Builtin or custom\n\n## Installation\n\n### CocoaPods\n\nRouteComposer is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod 'RouteComposer'\n```\n\n**For Xcode 10.1 / Swift 4.2 Support**\n\n```ruby\npod 'RouteComposer', '~\u003e 1.4'\n```\n\nAnd then run `pod install`.\n\nOnce successfully integrated, just add the following statement to any Swift file where you want to use RouteComposer:\n\n```swift\nimport RouteComposer\n```\n\nCheck out the Example app included, as it covers most of the general use cases.\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.\n\nOnce you have your Swift package set up, adding RouteComposer as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/ekazaev/route-composer\", .upToNextMajor(from: \"2.10.4\"))\n]\n```\n\n## Example\n\nTo run the example project, clone the repo, and run `pod install` from the Example directory first.\n\n## Requirements\n\nThere are no actual requirements to use this library. But if you are going to implement your custom containers\nand actions you should be familiar with the library concepts and UIKit's view controller navigation stack intricacies.\n\n### API documentation\n\nDetailed API documentation can be found [here](https://ekazaev.github.io/route-composer/). \nTest coverage - [here](https://codecov.io/gh/ekazaev/route-composer) \n\n## Testimonials\n\n#### Viz.ai\n\n\u003e At Viz.ai, the leading synchronised stroke care service, we went into replacing our entire navigation system, and we knew we needed to \naddress complex and dynamic navigation scenarios. Coordinators and other flow-control libraries just didn't answer our needs, and \nlead to mixing application logic and navigation, or creating massive coordinator classes.\n\u003e RouteComposer was an amazing fit for us, and actually, as the creator of this library states, it *is* the drop in replacement for any \ncoordinator code you currently use.\n\u003e\n\u003e The separation of concerns on this library is absolutely beautiful, and as with anything genius, it all works like magic.\nIt does have a small learning curve, but one that pays off far more than coordinators and flow controllers, and will save \nyou a ton of coding once you implement it.\n\u003e\n\u003eIt makes navigation in the app as simple as saying \"go to x with y\" and not worrying about the current state or stack.\nI wholeheartedly recommend it.\n\u003e\n\u003e**Elazar Yifrach, Sr iOS Developer @ Viz.ai**\n\n#### Hudson's Bay Company\n\n\u003e In our iOS app we wanted to provide a seamless experience for our users to guarantee that whenever they click on a \npush notification or a link in an email, they will land on the required view in the app seamlessly no matter of the state \nof the app.\n\u003e\n\u003e We tried a programmatic navigational approach in the code and also tried to rely on a few other libraries. However, \nnone of them seemed to do the trick. RouteComposer was not our first choice as originally it looked too complex. Thankfully,\nit turned out to be a fantastic and elegant solution. We started to use it not only to handle external deeplinking but \nalso to handle our internal navigation within the app.It also turned out to be a great tool for UI A/B tests when you \nhave different navigation patterns for different users. It saved us a load of time, and we really like the logic behind it.\n\u003e\n\u003e The creator of the library is super responsive and helped with all questions that we had. I would thoroughly recommend it!\n\u003e\n\u003e **Alexandra Mikhailouskaya, Senior lead engineer @ Hudson's Bay Company**\n\n#### B.W.A., 130 year old retail bank.\n\n\u003e We recently performed our fifth and largest app update which involved restructuring the user navigation from scratch. We started with a simple migration of our existing (six-file long) coordinator before one of our senior devs suggested we trial RouteComposer. The proof of concept was challenging, but Eugene Kazaev put himself at my disposal to work through retrofitting the RouteComposer into our existing enterprise-grade codebase and when the pieces all fell into place, the result was simplicity itself.\n\u003e \n\u003e Our other devs have embraced the RouteComposer in lieu of segues, unwind segues, manual pushes, pops, and modal drops and the resulting navigtion around our app is delightful.\n\u003e \n\u003e Great thanks to Eugene for all his help.\n\u003e **skooter Martin, Senior Specialist Mobile Engineer @ B.W.A.**\n\n## Sponsor this project\n\nIf you like this library and especially if you are using it in production please consider sponsoring this\nproject [here](https://github.com/sponsors/ekazaev). I work on `RouteComposer` in my spare time. Sponsorship\nwill help me to work on this project and continue to contribute to the Open Source community.\n\n## Usage\n\nRouteComposer uses 3 main entities (`Factory`, `Finder`, `Action`) that should be defined by a host application to support it.\nIt also provides 3 helping entities (`RoutingInterceptor`, `ContextTask`, `PostRoutingTask`) that you may implement to handle some\ndefault actions during the routing process. There are 2 `associatedtype` in the description of each entity below:\n* `ViewController` - Type of view controller. *UINavigationController, CustomViewController, etc.*\n* `Context` - Type of context object that is passed to the router from the hosting application that router will pass to the view controllers it\nis going to build. *String, UUID, Any, etc. Can be optional.*\n\n**NB**\n\n`Context` represents a payload that you need to pass to your `UIViewController` and something that distinguishes it from others.\nIt is not a View Model or some kind of Presenter. It is the missing piece of information. If your view controller requires a \n`productID` to display its content, and the `productID` is a `UUID`, then the type of `Context` is the `UUID`. The internal logic \nbelongs to the view controller. `Context` answers the questions *What to I need to present a ProductViewController* and *Am I \nalready presenting a ProductViewController for this product*.\n\n## Implementation\n\n#### 1. Factory\n\nFactory is responsible for **building view controllers**, that the router has to navigate to upon request.\nEvery Factory instance must implement the `Factory` protocol:\n\n```swift\npublic protocol Factory {\n\n    associatedtype ViewController: UIViewController\n\n    associatedtype Context\n\n    func build(with context: Context) throws -\u003e ViewController\n\n}\n```\n\nThe most important function here is `build` which should actually create the view controller. For detailed information\nsee the [documentation](https://ekazaev.github.io/route-composer/Protocols/Factory.html#/s:13RouteComposer7FactoryP5build4with14ViewControllerQz7ContextQz_tKF). \nThe `prepare` function provides you with a way of doing something before the routing actually takes place.\nFor example, you could `throw` from inside this function in order to inform the router that you do not have the data required to\ndisplay the view correctly. It may be useful if you are implementing Universal Links in your application and the routing can't be\nhandled, in which case the application might open the provided URL in Safari instead.\n\n*Example: Basic implementation of the factory for some custom `ProductViewController` view controller might look like:*\n\n```swift\nclass ProductViewControllerFactory: Factory {\n\n    func build(with productID: UUID) throws -\u003e ProductViewController {\n        let productViewController = ProductViewController(nibName: \"ProductViewController\", bundle: nil)\n        productViewController.productID = productID // Parameter initialisation can be handled by a ContextAction, see below:\n\n        return productViewController\n    }\n\n}\n```\n*Important note: Automatic `associatedtype` resolution is broken in Xcode 10.2, you must set associated types manually using `typealias` keyword. \nSwift compiler [bug](https://bugs.swift.org/browse/SR-10186) reported.*\n\n#### 2. Finder\n\nFinder helps router to **find out if a particular view controller is already present** in view controller graph. All the finder instances\nshould conform to `Finder` protocol.\n\n```swift\npublic protocol Finder {\n\n    associatedtype ViewController: UIViewController\n\n    associatedtype Context\n\n    func findViewController(with context: Context) throws -\u003e ViewController?\n\n}\n```\n\nIn some cases, you may use default finders provided by the library. In other cases, when you can have more than one view controller of\nthe same type in the graph, you may implement your own finder. There is an implementation of this protocol included called `StackIteratingFinder`\nthat helps to solve iterations in view controller graph and handles it. You just have to implement the function `isTarget` to determine if it's the\nview controller that you are looking for or not.\n\n*Example of `ProductViewControllerFinder` that can help the router find a `ProductViewController` that presents a particular\nproduct in your view controller stack:*\n\n```swift\nclass ProductViewControllerFinder: StackIteratingFinder {\n\n    let iterator: StackIterator = DefaultStackIterator()\n\n    func isTarget(_ productViewController: ProductViewController, with productID: UUID) -\u003e Bool {\n        return productViewController.productID == productID\n    }\n\n}\n```\n\n`SearchOptions` is an enum that informs `StackIteratingFinder` how to iterate through the graph when searching. See [documentation](https://ekazaev.github.io/route-composer/Structs/SearchOptions.html).\n\n#### 3. Action\n\nThe `Action` instance explains to the router **how the view controller is created by a `Factory` should be integrated into a view controller stack**.\nMost likely, you will not need to implement your own actions because the library provides actions for most of the default actions that can be done in\n`UIKit` like (`GeneralAction.presentModally`, `UITabBarController.add`, `UINavigationController.push` etc.). You may need to implement your own actions if you are\ndoing something unusual.\n\nCheck example app to see a custom action implementation.\n\n*Example: As you most likely will not need to implement your own actions, let's look at the implementation of `PresentModally` provided\nby the library:*\n\n```swift\nclass PresentModally: Action {\n\n    func perform(viewController: UIViewController, on existingController: UIViewController, animated: Bool, completion: @escaping (_: RoutingResult) -\u003e Void) {\n        existingController.present(viewController, animated: animated, completion: {\n            completion(.success)\n        })\n    }\n\n}\n```\n\n#### 4. Routing Interceptor\n\nRouting interceptor will be **used by the router before it will start routing to the target view controller.** For example, to navigate to\nsome particular view controller, the user might need to be logged in. You may create a class that implements the `RoutingInterceptor` protocol\nand if the user is not logged in, it will present a login view controller where the user can log in. If this process finishes successfully,\nthe interceptor should inform the router and it will continue routing or otherwise stop routing. See example app for details.\n\n*Example: If the user is logged in, router can continue routing. If the user is not logged in, the router should not continue*\n\n```swift\nclass LoginInterceptor\u003cC\u003e: RoutingInterceptor {\n\n    func perform(with context: C, completion: @escaping (_: RoutingResult) -\u003e Void) {\n        guard !LoginManager.sharedInstance.isUserLoggedIn else {\n            completion(.failure(\"User has not been logged in.\"))\n            return\n            // Or present the LoginViewController. See Example app for more information. \n        }\n        completion(.success)\n    }\n\n}\n\n```\n\n#### 5. Context Task\n\nIf you are using one default `Factory` and `Finder` implementation provided by the library, you still need to **set data in\ncontext to your view controller.** You have to do this even if it already exists in the stack, if it's just going to be created by a `Factory` or do any other\nactions at the moment when router found/created a view controller. Just implement `ContextTask` protocol.\n\n*Example: Even if `ProductViewController` is present on the screen or it is going to be created you have to set productID to\npresent a product.*\n\n```swift\nclass ProductViewControllerContextTask: ContextTask {\n\n    func perform(on productViewController: ProductViewController, with productID: UUID) {\n        productViewController.productID = productID\n    }\n\n}\n```\n\nSee example app for the details.\n\n*Or use `ContextSettingTask` provided with the library to avoid extra code.*\n\n#### 6. Post Routing Task\n\nA post-routing task will be called by the router **after it successfully finishes navigating to the target view controller**.\nYou should implement `PostRoutingTask` protocol and create all necessary actions there.\n\n*Example: You need to log an event in your analytics every time the user lands on a product view controller:*\n\n```swift\nclass ProductViewControllerPostTask: PostRoutingTask {\n\n    let analyticsManager: AnalyticsManager\n\n    init(analyticsManager: AnalyticsManager) {\n        self.analyticsManager = analyticsManager\n    }\n\n    func perform(on productViewController: ProductViewController, with productID: UUID, routingStack: [UIViewController]) {\n        analyticsManager.trackProductView(productID: productViewController.productID)\n    }\n\n}\n```\n\n### Configuring Step\n\nEverything that the router does is configured using a `DestinationStep` instance. There is no need to create your own implementation of this protocol.\nUse `StepAssembly` provided by the library to configure any step that the router should execute during the routing.\n\n*Example: A `ProductViewController` configuration that explains to the router that it should be boxed in UINavigationController\nwhich should be presented modally from any currently visible view controller.*\n\n```swift\nlet productScreen = StepAssembly(finder: ProductViewControllerFinder(), factory: ProductViewControllerFactory())\n        .add(LoginInterceptor\u003cUUID\u003e()) // Have to specify the context type till https://bugs.swift.org/browse/SR-8719, https://bugs.swift.org/browse/SR-8705 are fixed\n        .add(ProductViewControllerContextTask())\n        .add(ProductViewControllerPostTask(analyticsManager: AnalyticsManager.sharedInstance))\n        .using(UINavigationController.push())\n        .from(NavigationControllerStep())\n        .using(GeneralActions.presentModally())\n        .from(GeneralStep.current())\n        .assemble()\n```\n\nThis configuration means:\n\n* Use `ProductViewControllerFinder` to potentially **find** an existing product view controller in the stack, or **create** it using `ProductViewControllerFactory` if it has not been found.\n* If it was created **push** it into a navigation stack\n* Navigation stack should be provided from another step `NavigationControllerStep`, that will create a `UINavigationController` instance\n* The `UINavigationController` instance should be presented modally from any currently visible view controller.\n* Before routing run `LoginInterceptor`\n* After view controller been created or found, run `ProductViewControllerContextTask`\n* After successful routing run `ProductViewControllerPostTask`\n\n*See example app to find out different ways to provide and store routing step configurations.*\n\n*See advanced `ProductViewController` configuration [here](https://ekazaev.github.io/route-composer/examples.html#the-code-productviewcontroller-code-should-be-pushed-into-any-code-uinavigationcontroller-code-if-it-is-present-on-the-screen-if-not-presented-modally).*\n\n### Navigation\n\nAfter you have implemented all necessary classes and configured a routing step, you can start to use the `Router` to navigate. The library provides\na `DefaultRouter` which is an implementation of the `Router` protocol to handle routing based on the configuration explained above.\n\n*Example: The user taps on a cell in a `UITableView`. It then asks the router to navigate the user to `ProductViewController`. The user\nshould be logged into see the product details.*\n\n```swift\n\nstruct Configuration {\n\n    static let productScreen = StepAssembly(finder: ProductViewControllerFinder(), factory: ProductViewControllerFactory())\n                .add(LoginInterceptor\u003cUUID\u003e())\n                .add(ProductViewControllerContextTask())\n                .add(ProductViewControllerPostTask(analyticsManager: AnalyticsManager.sharedInstance))\n                .using(UINavigationController.push())\n                .from(NavigationControllerStep())\n                .using(GeneralActions.presentModally())\n                .from(GeneralStep.current())\n                .assemble()\n\n}\n\nclass ProductArrayViewController: UITableViewController {\n\n    let products: [UUID]?\n\n    let router = DefaultRouter()\n\n    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n        guard let productID = products[indexPath.row] else {\n            return\n        }\n        try? router.navigate(to: Configuration.productScreen, with: productID)\n    }\n\n}\n```\n\n*Example below shows the same process without the use of RouteComposer*\n\n```swift\nclass ProductArrayViewController: UITableViewController {\n\n    let products: [UUID]?\n\n    let analyticsManager = AnalyticsManager.sharedInstance\n\n    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n        guard let productID = products[indexPath.row] else {\n            return\n        }\n\n        // Handled by LoginInterceptor\n        guard !LoginManager.sharedInstance.isUserLoggedIn else {\n            return\n        }\n\n        // Handled by a ProductViewControllerFactory\n        let productViewController = ProductViewController(nibName: \"ProductViewController\", bundle: nil)\n\n        // Handled by ProductViewControllerContextTask\n        productViewController.productID = productID\n\n        // Handled by NavigationControllerStep and UINavigationController.push\n        let navigationController = UINavigationController(rootViewController: productViewController)\n\n        // handled by DefaultActions.PresentModally\n        present(navigationController, animated: true) { [weak self] in\n            // Handled by ProductViewControllerPostTask\n            self?.analyticsManager.trackProductView(productID: productID)\n        }\n    }\n\n}\n```\n\nIn the example without `RouteComposer` the code may seem simpler, however, everything is hardcoded in the actual function implementation. \n`RouteComposer` allows you to split everything into small reusable pieces and store navigation configuration separately from\nyour view logic. Also, the above implementation will grow dramatically when you try to add Universal Link support to your app.\nEspecially if you will have to choose from opening `ProductViewController` from a universal link if it is already present on the\nscreen or not and so on. With the library, each of your view controllers is deep linkable by nature.\n\nAs you can see from the examples above the `Router` does not do anything that tweaks `UIKit` basis. It just allows you to break the\nnavigation process into small reusable pieces. The router will call them in a proper order based on the configuration provided.\nThe library does not break the rules of VIPER or MVVM architectural patterns and can be used in parallel with them.\n\nSee example app for other examples of defining routing configurations and instantiating router.\n\n## Container View Controllers\n\nThere are view controllers like `UINavigationController`, `UITabBarController`, `UISplitController` and so on, that can contain \nother view controllers inside them. `RouteComposer` calls one such view controller, `ContainerViewController`s. As each container \nview controller has its own unique methods of interacting with the contained view controllers, `RouteComposer` uses special \nentities called [ContainerAdapter](https://ekazaev.github.io/route-composer/Protocols/ContainerAdapter.html)s. The `RouteComposer`\ncontains built-in adapters for the main container view controllers that come with `UIKit`. You can create your own `ContainerAdapter`s \nif you are using your own custom container view controllers or ones that come from another library. If you want `RouteComposer` to work \ncorrectly with such containers, switch their tabs or make another view controller visible within them e.t.c. \nPlease check the Example app for the reference.\n\n## Deep-linking\n\nWith `RouteComposer` every view controller becomes deep-linkable out of the box. You can also provide different configuration in case\nthe screen is being opened using universal link. See Example app for more information. \n\n```swift\n    let router = DefaultRouter()\n\n    func application(_ application: UIApplication,\n                     open url: URL,\n                     sourceApplication: String?,\n                     annotation: Any) -\u003e Bool {\n        guard let productID = extractProductId(from: url) else {\n            return false\n        }\n        try? router.navigate(to: Configuration.productScreen, with: productID)\n        return true\n    }\n```\n\n## Troubleshooting\n\nIf for some reason you are unsatisfied with the result and you think that it is the Routers issue, or you found that your particular case is not covered, you can always\ntemporarily replace the router with your custom implementation and implement simple routing yourself. Please, create a [new issue](https://github.com/ekazaev/route-composer/issues/new)\nand we will try to fix the issue as soon as possible.\n\n*Example:*\n```swift\n     func goToProduct(with productId: UUID) {\n        // If view controller with this product id is present on the screen - do nothing\n        guard ProductViewControllerFinder(options: .currentVisibleOnly).getViewController(with: productId) == nil else {\n            return\n        }\n        \n        /// Otherwise, find visible `UINavigationController`, build `ProductViewController`\n        guard let navigationController = ClassFinder\u003cUINavigationController, Any?\u003e(options: .currentVisibleOnly).getViewController(),\n              let productController = try? ProductViewControllerFactory().execute(with: productId) else {\n            return\n        }\n        \n        /// Apply context task if necessary\n        try? ProductViewControllerContextTask().execute(on: productController, with: productId)\n\n        /// Push `ProductViewController` into `UINavigationController`\n        navigationController.pushViewController(productController, animated: true)\n    }\n```\n\n## SwiftUI:\n\n`RouteComposer` is compatible with [SwiftUI](https://developer.apple.com/xcode/swiftui/). See example app for the details.\n\n## Advanced Configuration:\n\nYou can find more configuration examples [here](https://ekazaev.github.io/route-composer/examples.html).\n\n## Contributing\n\nRouteComposer is in active development, and we welcome your contributions.\n\nIf you’d like to contribute to this repo, please\nread [the contribution guidelines](https://github.com/ekazaev/route-composer/blob/master/CONTRIBUTING.md).\n\n## License\n\nRouteComposer is distributed under [the MIT license](https://github.com/ekazaev/RouteComposer/blob/master/LICENSE).\n\nRouteComposer is provided for your use, free-of-charge, on an as-is basis. We make no guarantees, promises or\napologies. *Caveat developer.*\n\n## Articles\n\nEnglish:\n\n  - [Composition of UIViewControllers and navigation between them](https://itnext.io/composition-of-uiviewcontrollers-and-navigation-between-them-and-not-only-15b825da5ac)\n  - [Going deeper into the RouteComposer configuration](https://itnext.io/going-deeper-into-the-routecomposer-configuration-3a54661bb16a)\n  - [Coordinator Pattern’s Issues \u0026 What is RouteComposer](https://itnext.io/coordinator-patterns-issues-what-is-routecomposer-8b50a0477917)\n\nRussian:\n\n  - [Композиция UIViewController-ов и навигация между ними](https://habr.com/post/421097/)\n  - [Примеры конфигурации UIViewController-ов используя RouteComposer](https://habr.com/post/428990/)\n  - [Проблемы паттерна Координатор и при чем тут RouteComposer](https://habr.com/ru/post/446550/)\n  - [Декларативная навигация в iOS-приложении — Андрей Зонов, Тинькофф](https://www.youtube.com/watch?v=0fT_xyBDl0w)\n\n## Author\n********\nEvgeny Kazaev, eugene.kazaev@gmail.com. Twitter [ekazaev](https://twitter.com/EKazaev)\n\n*I am happy to answer any questions you may have. Just create a [new issue](https://github.com/ekazaev/route-composer/issues/new).*\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekazaev%2Froute-composer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fekazaev%2Froute-composer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekazaev%2Froute-composer/lists"}