{"id":770,"url":"https://github.com/avito-tech/Marshroute","last_synced_at":"2025-07-30T19:32:28.068Z","repository":{"id":8885759,"uuid":"60022229","full_name":"avito-tech/Marshroute","owner":"avito-tech","description":"Marshroute is an iOS Library for making your Routers simple but extremely powerful","archived":false,"fork":false,"pushed_at":"2024-03-15T07:12:10.000Z","size":25969,"stargazers_count":224,"open_issues_count":1,"forks_count":30,"subscribers_count":24,"default_branch":"master","last_synced_at":"2024-11-29T06:17:18.791Z","etag":null,"topics":["hacktoberfest","hacktoberfest2021","ios","navigation","router","swift","transition","viper"],"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/avito-tech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2016-05-30T15:58:07.000Z","updated_at":"2024-07-14T12:08:23.000Z","dependencies_parsed_at":"2024-04-21T18:11:32.263Z","dependency_job_id":null,"html_url":"https://github.com/avito-tech/Marshroute","commit_stats":{"total_commits":431,"total_committers":11,"mean_commits":39.18181818181818,"dds":"0.10904872389791187","last_synced_commit":"7636e636ad258b9d6d03cf6dcbf9f3fbffcea1f8"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avito-tech%2FMarshroute","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avito-tech%2FMarshroute/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avito-tech%2FMarshroute/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avito-tech%2FMarshroute/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avito-tech","download_url":"https://codeload.github.com/avito-tech/Marshroute/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228178890,"owners_count":17881105,"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":["hacktoberfest","hacktoberfest2021","ios","navigation","router","swift","transition","viper"],"created_at":"2024-01-05T20:15:30.952Z","updated_at":"2024-12-04T19:31:58.976Z","avatar_url":"https://github.com/avito-tech.png","language":"Swift","funding_links":[],"categories":["App Routing"],"sub_categories":["Other free courses","Getting Started"],"readme":"# Marshroute\n[![GitHub license](https://img.shields.io/badge/License-MIT-lightgrey.svg)](https://github.com/avito-tech/Marshroute/blob/master/LICENSE) [![GitHub release](https://img.shields.io/badge/Version-1.0.0-brightgreen.svg)](https://github.com/avito-tech/Marshroute/releases)  [![Swift 5.0 support](https://img.shields.io/badge/Swift%205.0-supported-brightgreen.svg)](https://github.com/avito-tech/Marshroute/commit/278beaa9b53476315906fee1ba6555caf2e1db86) [![cocoapods compatible](https://img.shields.io/badge/Cocoapods-compatible-blue.svg)](https://cocoapods.org) \n\n## Contents\n\n* [Overview](#overview)\n    * [Detailes](#overview-details)\n    * [Tuning the transition animation](#tuning-the-transition-animation)\n* [3d touch support](#3d-touch-support)\n    * [PeekAndPopUtility](#peek-and-pop-utility)\n    * [Peek and pop state observing](#peek-and-pop-state-observing)\n* [Demo](#demo)\n* [Requirements](#requirements)\n* [Installation](#installation)\n    * [Cocoapods](#cocoapods)\n    * [Carthage](#carthage)\n* [Customization](#plugin-customization)\n* [Licence](#licence)\n* [Objective-c support](#objective-c-support)\n* [Useful links](#useful-links)\n* [Authors](#authors)\n\n## \u003ca name=\"overview\"/\u003e Overview\n\n `Marshroute` is a library that will encourage you to locate all the navigation logic in the `Router` layer, no matter which architecture you prefer. \n `Marshroute` helps make your `Router`s syntactically compact and clear. \n\nKey features:\n- Every `Router`-driven transition is always forwarded to the topmost `UIViewController`. This means you can ask `Marshroute` to present a view controller from any point in your program and it will simply work!\n- No matter how you module was presented, you can simply ask your module's `Router` to dismiss this module via calling `router.dismissCurrentModule()` and it will simply work! Your parent module can change presentation style in the future (e.g. present modally instead of pushing), but `router.dismissCurrentModule()` will work anyway!\n- No matter how your module presents subsequent modules, you can simply ask your module's `Router` to return to this module via calling `router.focusOnCurrentModule()` and it will also simply work!\n- `Marshroute` allows changing transition animations in just 1 line of code (see [Tuning the transition animation](#tuning-the-transition-animation) for more details)\n- `Marshroute` supports 3d touch transitions\n- `Marshroute` detects view controller retain cycles and notifies you about them via assertions API. You can override default assertions with your implementation: e.g. print assertions to the output or do some advanced analytics (see [plugin-customization](#plugin-customization) for more details)\n- `Marshroute` features a detailed [demo](#demo) project describing key navigation principles on both iPhone and iPad\n\n### \u003ca name=\"overview-detailes\"/\u003e Details\n\nEvery `Router`-driven transition is always forwarded to the topmost `UIViewController`\nto make it super easy to support `DeepLink`s and for example present `Authorization` module from any point of your application. \nI prefer doing this right from my root application's `Router`.\n\nThis repo allows you to drive your transitions in a super clean, descriptive and flexible fashion. \nFor example pretend the following code is taken from your root application's `Router`: \n\n```swift\nfunc showAuthorization() {\n    pushViewControllerDerivedFrom { routerSeed -\u003e UIViewController in\n        let authorizationAssembly = assemblyFactory.authorizationAssembly()\n\n        let viewController = authorizationAssembly.module(\n            routerSeed: routerSeed\n        )\n\n        return viewController\n    }\n}\n```\n\nThis code pushes an `Authorization` view controller to the top `UINavigationController`'s stack.\nThe `routerSeed` parameter is only used to create a `Router` for the `Authorization` module.\n\nThe magic here is in this line of code:\n```swift\npushViewControllerDerivedFrom { routerSeed -\u003e UIViewController in\n```\n\nYou can easily change the presentation style in favor of a modal transition by simply changing it to: \n```swift\npresentModalNavigationControllerWithRootViewControllerDerivedFrom { routerSeed -\u003e UIViewController in\n```\n\nIf for some reason you do not need a `UINavigationController` for your `Authorization` module, you may accomplish this by:\n```swift\npresentModalViewControllerDerivedFrom { routerSeed -\u003e UIViewController in\n```\n\nOnce again, the transition will be forwarded to the top, keeping the `Router` very plain and straightforward.\nSo that, the `Router` keeps being responsible for only one thing: selecting the style of a transition. \n\n### \u003ca name=\"tuning-the-transition-animation\"/\u003e Tuning the transition animation\n\nYou may add an animator to customize the way your transition looks like. For example\n\n```swift\nfunc showCategories() {\n    presentModalNavigationControllerWithRootViewControllerDerivedFrom( { routerSeed -\u003e UIViewController in\n        let categoriesAssembly = assemblyFactory.categoriesAssembly()\n\n        let viewController = categoriesAssembly.module(\n            routerSeed: routerSeed\n        )\n\n        return viewController\n    }, animator: RecursionAnimator())\n}\n```\n\nThe key line here is \n```swift\n}, animator: RecursionAnimator())\n```\n\nSo the syntax remains clean and it is super easy to switch back to the original animation style.\n\n## \u003ca name=\"3d-touch-support\"/\u003e 3d touch support\n\n### \u003ca name=\"peek-and-pop-utility\"/\u003e PeekAndPopUtility\n\nWant to add fancy peek and pop previews? Easy peasy! Just use `PeekAndPopUtility` from the `MarshrouteStack` and register your view controller as capable of previewing other controllers!\n\n```swift\npeekAndPopUtility.register(\n    viewController: self,\n    forPreviewingInSourceView: peekSourceView,\n    onPeek: { [weak self] (previewingContext, location) in\n        self?.startPeekWith(\n            previewingContext: previewingContext,\n            location: location\n        )\n    },\n    onPreviewingContextChange: nil\n)\n```\n\n`peekSourceView` is used by `UIKit` during preview animations to take screenshots from. You can register single view controller for previewing in many source views (e.g.: in a table view and in a navigation bar).\n\n`onPeek` closure will get called every time a force touch gesture occurs in a `peekSourceView`. In your `startPeekWith(previewingContext:location:)` method you should do the following:\n\n1. Find a view which a user interacts with (interactable view). You should use a specified `location` in `previewingContext.sourceView`'s coordinate system.\n\n1. Adjust `sourceRect` of a `previewingContext`. You should convert a frame of that interactable view to `previewingContext.sourceView`'s coordinate system. `UIKit` uses `sourceRect` to keep it visually sharp when a user presses it, while blurring all surrounding content.\n\n1. Invoke the transition, that will normally occur if a user simply taps at a same `location`.\nFor example, it a user presses a `UIControl`, you may call `sendActions(for: .touchUpInside)` to invoke that `UIControl`'s an action handler. \n\nLets pretend the above-mentioned action handler ends up with some router calling `pushViewControllerDerivedFrom(_:)` to push a new view controller.\nIn this case no pushing will actually occur. Instead of this, `Marshroute` will freeze a transition and present a target view controller in a preview mode.\nThe transition will eventually get performed only if a user commits the preview (i.e. pops).\n\nThe above described behavior takes place only during active `peek` requests (when `UIViewControllerPreviewingDelegate` requests a view controller to be previewed).\nIn all other situations, `pushViewControllerDerivedFrom(_:)` will push immediately as expected.\n\nImportant note: if you invoke no transition within `onPeek` closure, or invoke an asynchronous transition, no `peek` will occur.\nThis behavior is a result of `UIKit` Api restrictions: `UIViewControllerPreviewingDelegate` is required to return a previewing view controller synchronously.\n\nYou can also use `onPreviewingContextChange` closure to set up your gesture recognizer failure relationships.\n\n### \u003ca name=\"peek-and-pop-state-observing\"/\u003e Peek and pop state observing\n\nYou can use `PeekAndPopStateObservable` from the `MarshrouteStack` to observe any view controller's `peek and pop` state changes. \nThis may be useful for analytics purposes.\n\n\n```swift\npeekAndPopStateObservable.addObserver(\n    disposable: self,\n    onPeekAndPopStateChange: { viewController, peekAndPopState in\n        debugPrint(\"viewController: \\(viewController) changed `peek and pop` state: \\(peekAndPopState)\")\n    }\n)\n```\n\nYou can also use `PeekAndPopStateViewControllerObservable` to observe your particular view controller's `peek and pop` state changes. \nThis may be useful for adjusting view controller's appearance in `peek` and `popped` modes.\n\n```swift\npeekAndPopStateViewControllerObservable.addObserver(\n    disposableViewController: self,\n    onPeekAndPopStateChange: { [weak self] peekAndPopState in\n        switch peekAndPopState {\n        case .inPeek:\n            self?.onPeek?()\n        case .popped:\n            self?.onPop?()\n        case .interrupted:\n            break\n        }\n    }\n)\n```\n\nHere in `onPeek` and `onPop` closures your `Presenter` may force a view to update its UI accordingly\n\n```swift\nview?.onPeek = { [weak self] in\n    self?.view?.setSimilarSearchResultsHidden(true)\n}\n\nview?.onPop = { [weak self] in\n    self?.view?.setSimilarSearchResultsHidden(false)\n}\n```\n\n## \u003ca name=\"demo\"/\u003e Demo\n\nCheck out the [demo](https://github.com/avito-tech/Marshroute/tree/master/Example) project. \nThis demo is written in `Swift` using `VIPER` architecture and shows all the capabilities which `Router`s are now full of.\n\nRun this demo on a simulator and check out what happens if you simulate a memory warning or a device shake. \nYou will see several types of transitions driven by the root module's `Router` (i.e. a `UITabBarController`'s `Router`).\n\nThe demo project targets both `iPhone` and `iPad` and adds some minor differences to their navigation behaviors by creating distinct `Router` implementations for every supported device idiom, thus highlighting the value of moving the navigation logic from the `View` layer in favor of a `Router` layer.\n\nWhen you tap a blue timer tile, you schedule a reverse transition to the module that tile belongs to. \nTo see this effect taking place, you should make several transitions deeper into the navigation stack.\n\nStarting with 0.4.0 the demo project was updated to show `PeekAndPopUtility` in action: you can press on any table view cell and navigation bar button to get a preview of an underlying transition.\nYou can also learn how to use `PeekAndPopStateViewControllerObservable` to adjust `AdvertisementViewController`'s appearance in `peek` and `popped` modes: in a `peek` mode you will see only a fullscreen colored image pattern, while in a `popped` mode you will also see a similar advertisements section.\n\n## \u003ca name=\"requirements\"/\u003e Requirements\n\n- iOS 9.0+\n- Xcode 14.0+\n\nNote: peek and pop is supported only for iOS 9.0+\n\n## \u003ca name=\"installation\"/\u003e Installation\n### \u003ca name=\"cocoapods\"/\u003e Cocoapods\n\nTo install Marshroute using CocoaPods, add the following lines to your `Podfile`:\n\n```ruby\nsource 'https://github.com/CocoaPods/Specs.git'\nplatform :ios, '9.0'\nuse_frameworks!\n\npod 'Marshroute'\n```\n\nThen run `pod install` command. For details of the installation and usage of CocoaPods, visit [its official website](https://cocoapods.org).\n\n### \u003ca name=\"carthage\"/\u003e Carthage\n\nTo install Marshroute using Carthage, add the following lines to your `Cartfile`:\n```ruby\ngithub \"avito-tech/Marshroute\" ~\u003e 1.0.0\n```\n\nThen run `carthage update --platform iOS` command. For details of the installation and usage of Carthage, visit [its  repo website](https://github.com/Carthage/Carthage).\n\n## \u003ca name=\"plugin-customization\"/\u003e Customization\n\nYou can provide custom print and assert realization using `MarshroutePrintPlugin` and `MarshrouteAssertionPlugin`.\nThis is as easy as:\n```\nMarshroutePrintManager.setUpPrintPlugin(YourPrintPlugin())\nMarshrouteAssertionManager.setUpAssertionPlugin(YourAssertionPlugin())\n```\n\n## \u003ca name=\"licence\"/\u003e Licence\nMIT\n\n## \u003ca name=\"objective-c-support\"/\u003e Objective-c support\nThe framework is written in pure `Swift` using its latest features, so if you want to use `Marshroute` in your `Objective-c` application you will have to write your `Router`s in `Swift`.\n\n## \u003ca name=\"useful-links\"/\u003e Useful links\nYou can watch [this video](https://www.youtube.com/watch?v=kyOm_dC038s\u0026t=1s) to get a closer look at the reasons and ideas which formed the basis of `Marshroute` (in Russian).\n\nYou can also read [this guide](https://github.com/avito-tech/Marshroute/wiki/Examples) of using `Marshroute` when implementing `DeepLink`s support in your application.\n\n## \u003ca name=\"authors\"/\u003e Authors\nTimur Yusipov (tyusipov@avito.ru, ykylele@gmail.com, https://twitter.com/Fizmatchel, https://stackoverflow.com/users/2982854/tim).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favito-tech%2FMarshroute","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favito-tech%2FMarshroute","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favito-tech%2FMarshroute/lists"}