├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── .gitignore
├── .jazzy.yaml
├── .slather.yml
├── .swiftformat
├── .swiftlint.yml
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── EXAMPLES.md
├── Example
├── .gitignore
├── .swiftlint.yml
├── Podfile
├── Podfile.lock
├── Pods
│ ├── Local Podspecs
│ │ ├── DeepLinkLibrary.podspec.json
│ │ └── RouteComposer.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── RouteComposer.xcscheme
│ ├── SwiftFormat
│ │ ├── CommandLineTool
│ │ │ └── swiftformat
│ │ ├── LICENSE.md
│ │ └── README.md
│ └── Target Support Files
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests-Info.plist
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests-acknowledgements.markdown
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests-acknowledgements.plist
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests-dummy.m
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests-frameworks.sh
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests-umbrella.h
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests.debug.xcconfig
│ │ ├── Pods-RouteComposer_Example-RouteComposer_Tests.modulemap
│ │ └── Pods-RouteComposer_Example-RouteComposer_Tests.release.xcconfig
│ │ ├── Pods-RouteComposer_Example
│ │ ├── Info.plist
│ │ ├── Pods-RouteComposer_Example-Info.plist
│ │ ├── Pods-RouteComposer_Example-acknowledgements.markdown
│ │ ├── Pods-RouteComposer_Example-acknowledgements.plist
│ │ ├── Pods-RouteComposer_Example-dummy.m
│ │ ├── Pods-RouteComposer_Example-frameworks.sh
│ │ ├── Pods-RouteComposer_Example-resources.sh
│ │ ├── Pods-RouteComposer_Example-umbrella.h
│ │ ├── Pods-RouteComposer_Example.debug.xcconfig
│ │ ├── Pods-RouteComposer_Example.modulemap
│ │ └── Pods-RouteComposer_Example.release.xcconfig
│ │ ├── RouteComposer
│ │ ├── Info.plist
│ │ ├── RouteComposer-Info.plist
│ │ ├── RouteComposer-dummy.m
│ │ ├── RouteComposer-prefix.pch
│ │ ├── RouteComposer-umbrella.h
│ │ ├── RouteComposer.debug.xcconfig
│ │ ├── RouteComposer.modulemap
│ │ ├── RouteComposer.release.xcconfig
│ │ └── RouteComposer.xcconfig
│ │ └── SwiftFormat
│ │ ├── SwiftFormat.debug.xcconfig
│ │ ├── SwiftFormat.release.xcconfig
│ │ └── SwiftFormat.xcconfig
├── RouteComposer.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── RouteComposer-Example.xcscheme
├── RouteComposer.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── RouteComposer
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── LaunchScreen.xib
│ │ ├── PromptScreen.storyboard
│ │ ├── Split.storyboard
│ │ ├── StarViewController.xib
│ │ └── TabBar.storyboard
│ ├── Configuration
│ │ ├── AnalyticsRouterDecorator.swift
│ │ ├── BlurredBackgroundPresentationController.swift
│ │ ├── BlurredBackgroundTransitionAnimator.swift
│ │ ├── BlurredBackgroundTransitionController.swift
│ │ ├── ExampleAnalyticsSupport.swift
│ │ ├── ExampleConfiguration.swift
│ │ ├── ExampleGenericContextTask.swift
│ │ ├── ExampleScreenTypes.swift
│ │ └── FailingRouter.swift
│ ├── Extensions
│ │ ├── Router+Destination.swift
│ │ ├── UIColor.swift
│ │ └── ViewController.swift
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── icon-AppStore.png
│ │ │ ├── icon-iPad76.png
│ │ │ ├── icon-iPad76@2x.png
│ │ │ ├── icon-iPad83@2x.png
│ │ │ ├── icon-iPhone60@2x.png
│ │ │ └── icon-iPhone60@3x.png
│ │ ├── Contents.json
│ │ ├── first.imageset
│ │ │ ├── Contents.json
│ │ │ └── first.pdf
│ │ ├── second.imageset
│ │ │ ├── Contents.json
│ │ │ └── second.pdf
│ │ └── star.imageset
│ │ │ ├── Contents.json
│ │ │ ├── star.png
│ │ │ ├── star@2x.png
│ │ │ └── star@3x.png
│ ├── Info.plist
│ ├── Libraries
│ │ ├── ContainerViewController
│ │ │ ├── ContainerViewController.xcodeproj
│ │ │ │ ├── project.pbxproj
│ │ │ │ └── project.xcworkspace
│ │ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ │ └── xcshareddata
│ │ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── ContainerViewController
│ │ │ │ ├── ContainerViewController.h
│ │ │ │ ├── CustomContainerController.swift
│ │ │ │ ├── CustomViewControllerDelegate.swift
│ │ │ │ └── Info.plist
│ │ ├── ImageDetailsController
│ │ │ ├── ImageDetailsController.xcodeproj
│ │ │ │ ├── project.pbxproj
│ │ │ │ └── project.xcworkspace
│ │ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ │ └── xcshareddata
│ │ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── ImageDetailsController
│ │ │ │ ├── ImageDetailsController.h
│ │ │ │ ├── ImageDetailsControllerDelegate.swift
│ │ │ │ ├── ImageDetailsFetcher.swift
│ │ │ │ ├── ImageDetailsViewController.swift
│ │ │ │ └── Info.plist
│ │ └── ImagesController
│ │ │ ├── ImagesController.xcodeproj
│ │ │ ├── project.pbxproj
│ │ │ └── project.xcworkspace
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── ImagesController
│ │ │ ├── ImagesController.h
│ │ │ ├── ImagesControllerDelegate.swift
│ │ │ ├── ImagesFetcher.swift
│ │ │ ├── ImagesViewController.swift
│ │ │ └── Info.plist
│ ├── Login.storyboard
│ ├── SceneDelegate.swift
│ ├── URLTranslators
│ │ ├── ColorURLTranslator.swift
│ │ ├── ExampleURLTranslator.swift
│ │ └── ExampleUniversalLinksManager.swift
│ └── View Controllers
│ │ ├── CircleViewController.swift
│ │ ├── Cities
│ │ ├── CitiesConfiguration.swift
│ │ ├── CitiesDataModel.swift
│ │ ├── CitiesTableViewController.swift
│ │ ├── CityDetailViewController.swift
│ │ └── CityURLTranslator.swift
│ │ ├── ColorViewController.swift
│ │ ├── ExampleNavigationController.swift
│ │ ├── FiguresViewController.swift
│ │ ├── Internal Search Demo
│ │ ├── AnyContextCheckingViewController.swift
│ │ ├── HomeViewController.swift
│ │ ├── InternalSearchConfiguration.swift
│ │ ├── MainScreenContext.swift
│ │ └── SettingsViewController.swift
│ │ ├── Login
│ │ ├── LoginConfiguration.swift
│ │ └── LoginViewController.swift
│ │ ├── No Library Dependency
│ │ ├── Configuration With Library
│ │ │ ├── CustomContainerFactory.swift
│ │ │ ├── ImageDetailsFactory.swift
│ │ │ ├── ImagesConfigurationWithLibrary.swift
│ │ │ ├── ImagesFactory.swift
│ │ │ └── ImagesWithLibraryHandler.swift
│ │ ├── Configuration Without Library
│ │ │ ├── ImagesWithoutLibraryConfiguration.swift
│ │ │ └── ImagesWithoutLibraryHandler.swift
│ │ ├── ImageDetailsFetcherImpl.swift
│ │ ├── ImageFetcherImpl.swift
│ │ └── Images.storyboard
│ │ ├── Product
│ │ ├── ProductConfiguration.swift
│ │ ├── ProductURLTranslator.swift
│ │ └── ProductViewController.swift
│ │ ├── PromptViewController.swift
│ │ ├── RoutingRuleSupportViewController.swift
│ │ ├── SecondModalLevelViewController.swift
│ │ ├── SquareViewController.swift
│ │ ├── StarViewController.swift
│ │ ├── SwiftUIContentView.swift
│ │ └── WishList
│ │ ├── WishListConfiguration.swift
│ │ ├── WishListContext.swift
│ │ ├── WishListContextTask.swift
│ │ └── WishListViewController.swift
├── RouteComposer_ExampleUITests
│ ├── AllRoutesInAppUITests.swift
│ ├── Info.plist
│ ├── ShortUITests.swift
│ └── SwiftUITests.swift
└── Tests
│ ├── ActionTests.swift
│ ├── AssemblyTest.swift
│ ├── BoxTest.swift
│ ├── ContainerLocatorTests.swift
│ ├── ContainerTests.swift
│ ├── DestinationStepTests.swift
│ ├── ErrorTest.swift
│ ├── ExtensionsTest.swift
│ ├── ExtrasTest.swift
│ ├── FactoryTest.swift
│ ├── FinderTests.swift
│ ├── Helpers
│ ├── EmptyContainer.swift
│ ├── EmptyFactory.swift
│ ├── TestStoryboard.storyboard
│ ├── TestSwiftUIView.swift
│ └── TestWindowProvider.swift
│ ├── Info.plist
│ ├── MultiplexerTest.swift
│ └── RouterTest.swift
├── Gemfile
├── LICENSE
├── Package.swift
├── README.md
├── RouteComposer.podspec
├── RouteComposer.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── RouteComposer
├── Assets
│ └── .gitkeep
├── Classes
│ ├── .gitkeep
│ ├── AbstractAction.swift
│ ├── AbstractFactory.swift
│ ├── Action.swift
│ ├── Actions
│ │ ├── UINavigationController+Action.swift
│ │ ├── UISplitViewController+Action.swift
│ │ ├── UITabBarController+Action.swift
│ │ └── UIViewController+Action.swift
│ ├── Adapters
│ │ ├── ConcreteContainerAdapter.swift
│ │ ├── CustomContainerViewController.swift
│ │ ├── DefaultContainerAdapterLocator.swift
│ │ ├── NavigationControllerAdapter.swift
│ │ ├── SplitControllerAdapter.swift
│ │ └── TabBarControllerAdapter.swift
│ ├── Assemblies
│ │ ├── ChainAssembly.swift
│ │ ├── CompleteFactoryAssembly.swift
│ │ ├── Helpers
│ │ │ ├── ActionConnectingAssembly.swift
│ │ │ ├── ActionToStepIntegrator.swift
│ │ │ ├── BaseEntitiesCollector.swift
│ │ │ ├── CompleteFactoryChainAssembly.swift
│ │ │ ├── ContainerStepChainAssembly.swift
│ │ │ ├── GenericStepAssembly.swift
│ │ │ ├── InterceptableStepAssembling.swift
│ │ │ ├── LastStepInChainAssembly.swift
│ │ │ ├── StepChainAssembly.swift
│ │ │ └── TaskCollector.swift
│ │ ├── StepAssembly.swift
│ │ └── SwitchAssembly.swift
│ ├── ChildCoordinator.swift
│ ├── ContainerAdapter.swift
│ ├── ContainerAdapterLocator.swift
│ ├── ContainerFactory.swift
│ ├── ContainerViewController.swift
│ ├── ContextTask.swift
│ ├── ContextTransformer.swift
│ ├── DestinationStep.swift
│ ├── Extensions
│ │ ├── Array+Extension.swift
│ │ ├── NavigationController+Extension.swift
│ │ ├── SplitViewController+Extension.swift
│ │ ├── TabBarViewController+Extension.swift
│ │ ├── UIViewController+Extension.swift
│ │ └── UIWindow+Extension.swift
│ ├── Extra
│ │ ├── CATransaction+Action.swift
│ │ ├── ContextAccepting.swift
│ │ ├── ContextChecking.swift
│ │ ├── ContextSettingTask.swift
│ │ ├── Destination.swift
│ │ ├── DetailsNavigationFinder.swift
│ │ ├── DismissalMethodProvidingContextTask.swift
│ │ ├── Dismissible.swift
│ │ ├── DispatchQueue+Action.swift
│ │ ├── GlobalInterceptorRouter.swift
│ │ ├── InlineContextTask.swift
│ │ ├── InlineContextTransformer.swift
│ │ ├── InlineFactory.swift
│ │ ├── InlineInterceptor.swift
│ │ ├── InlinePostTask.swift
│ │ ├── InlineStackIteratingFinder.swift
│ │ ├── NavigationDelayInterceptor.swift
│ │ ├── PresentingFinder.swift
│ │ ├── Router+Destination.swift
│ │ ├── SingleNavigationRouter.swift
│ │ └── SwiftUI
│ │ │ ├── ContextAcceptingView.swift
│ │ │ └── ContextInstantiatable.swift
│ ├── Factories
│ │ ├── ClassFactory.swift
│ │ ├── CompleteFactory.swift
│ │ ├── FinderFactory.swift
│ │ ├── NavigationControllerFactory.swift
│ │ ├── NilFactory.swift
│ │ ├── SimpleContainerFactory.swift
│ │ ├── SplitControllerFactory.swift
│ │ ├── StoryboardFactory.swift
│ │ ├── SwiftUI
│ │ │ ├── UIHostingControllerFactory.swift
│ │ │ └── UIHostingControllerWithContextFactory.swift
│ │ └── TabBarControllerFactory.swift
│ ├── Factory.swift
│ ├── Finder.swift
│ ├── Finders
│ │ ├── ClassFinder.swift
│ │ ├── ClassWithContextFinder.swift
│ │ ├── InstanceFinder.swift
│ │ ├── NilFinder.swift
│ │ ├── Stack Iterator
│ │ │ ├── CustomWindowProvider.swift
│ │ │ ├── DefaultStackIterator.swift
│ │ │ ├── KeyWindowProvider.swift
│ │ │ ├── StackIterator.swift
│ │ │ └── WindowProvider.swift
│ │ ├── StackIteratingFinder.swift
│ │ └── SwiftUI
│ │ │ └── UIHostingControllerWithContextFinder.swift
│ ├── InterceptableRouter.swift
│ ├── Logger
│ │ ├── DefaultLogger+LogLevel.swift
│ │ ├── DefaultLogger.swift
│ │ ├── LogMessage.swift
│ │ └── Logger.swift
│ ├── PostRoutingTask.swift
│ ├── RouteComposerDefaults.swift
│ ├── Router.swift
│ ├── Router
│ │ ├── DefaultRouter.swift
│ │ ├── Helpers
│ │ │ ├── DefaultStackPresentationHandler.swift
│ │ │ └── StackPresentationHandler.swift
│ │ ├── Internal
│ │ │ ├── Array+PrivateExtension.swift
│ │ │ ├── BaseStep.swift
│ │ │ ├── ChainableStep.swift
│ │ │ ├── ConvertingStep.swift
│ │ │ ├── DefaultRouter+Extension.swift
│ │ │ ├── InPlaceTransformingAnyContext.swift
│ │ │ ├── InterceptableStep.swift
│ │ │ ├── NilContextTransformer.swift
│ │ │ ├── NilEntity.swift
│ │ │ ├── PerformableStep.swift
│ │ │ ├── PerformableStepResult.swift
│ │ │ ├── PostponedIntegrationFactory.swift
│ │ │ ├── PreparableEntity.swift
│ │ │ ├── RoutingStep.swift
│ │ │ ├── SwitcherStep.swift
│ │ │ └── UIViewController+PrivateExtension.swift
│ │ ├── Multiplexers
│ │ │ ├── ContextTaskMultiplexer.swift
│ │ │ ├── InterceptorMultiplexer.swift
│ │ │ └── PostRoutingTaskMultiplexer.swift
│ │ └── Type Erasure
│ │ │ ├── AnyAction.swift
│ │ │ ├── AnyContext.swift
│ │ │ ├── AnyContextTask.swift
│ │ │ ├── AnyContextTransformer.swift
│ │ │ ├── AnyFactory.swift
│ │ │ ├── AnyFinder.swift
│ │ │ ├── AnyPostRoutingTask.swift
│ │ │ ├── AnyRoutingInterceptor.swift
│ │ │ └── Boxes
│ │ │ ├── ActionBox.swift
│ │ │ ├── AnyActionBox.swift
│ │ │ ├── AnyContextBox.swift
│ │ │ ├── AnyFactoryBox.swift
│ │ │ ├── ContainerActionBox.swift
│ │ │ ├── ContainerFactoryBox.swift
│ │ │ ├── ContextTaskBox.swift
│ │ │ ├── ContextTransformerBox.swift
│ │ │ ├── FactoryBox.swift
│ │ │ ├── FinderBox.swift
│ │ │ ├── PostRoutingTaskBox.swift
│ │ │ └── RoutingInterceptorBox.swift
│ ├── RoutingError.swift
│ ├── RoutingInterceptable.swift
│ ├── RoutingInterceptor.swift
│ ├── RoutingResult.swift
│ ├── SearchOptions.swift
│ └── Steps
│ │ ├── GeneralStep.swift
│ │ ├── NavigationControllerStep.swift
│ │ ├── SingleContainerStep.swift
│ │ ├── SingleStep.swift
│ │ ├── SplitControllerStep.swift
│ │ └── TabBarControllerStep.swift
├── Info.plist
└── RouteComposer.h
├── _Pods.xcodeproj
└── docs
├── Additional Assemblies.html
├── Assemblies.html
├── Classes
├── ActionToStepIntegrator.html
├── CompleteFactoryAssembly.html
├── CompleteFactoryChainAssembly.html
├── GenericStepAssembly.html
├── InlineContextTransformer.html
├── NavigationControllerStep.html
├── RouteComposerDefaults.html
├── SingleContainerStep.html
├── SingleNavigationLock.html
├── SingleStep.html
├── SplitControllerStep.html
├── StepAssembly.html
├── SwitchAssembly.html
└── TabBarControllerStep.html
├── Core Entities.html
├── Enums
├── ChainAssembly.html
├── GeneralAction.html
├── GeneralStep.html
├── LogMessage.html
├── NavigationControllerActions.html
├── NavigationControllerActions
│ ├── PushAction.html
│ ├── PushAsRootAction.html
│ └── PushReplacingLastAction.html
├── RoutingError.html
├── RoutingError
│ ├── Context.html
│ └── InitialControllerErrorState.html
├── RoutingResult.html
├── SplitViewControllerActions.html
├── SplitViewControllerActions
│ ├── PushOnToDetailsAction.html
│ ├── PushToDetailsAction.html
│ └── SetAsMasterAction.html
├── TabBarControllerActions.html
├── TabBarControllerActions
│ └── AddTabAction.html
├── ViewControllerActions.html
└── ViewControllerActions
│ ├── NilAction.html
│ ├── PresentModallyAction.html
│ ├── PresentModallyAction
│ └── ModalPresentationStartingPoint.html
│ └── ReplaceRootAction.html
├── Extensions
├── Array.html
├── CATransaction.html
├── DispatchQueue.html
├── UIHostingController.html
├── UINavigationController.html
├── UISplitViewController.html
├── UITabBarController.html
├── UIViewController.html
└── UIWindow.html
├── Extras.html
├── Factories.html
├── Finders.html
├── General Actions.html
├── Logging.html
├── Other Classes.html
├── Other Enums.html
├── Other Extensions.html
├── Other Guides.html
├── Other Protocols.html
├── Other Structs.html
├── Protocols
├── AbstractAction.html
├── AbstractFactory.html
├── ConcreteContainerAdapter.html
├── ContainerAction.html
├── ContainerAdapter.html
├── ContainerAdapterLocator.html
├── ContainerFactory.html
├── ContainerViewController.html
├── ContextAccepting.html
├── ContextAcceptingView.html
├── ContextChecking.html
├── ContextInstantiatable.html
├── ContextTask.html
├── ContextTransformer.html
├── CustomContainerViewController.html
├── Dismissible.html
├── DismissibleWithRuntimeStorage.html
├── Factory.html
├── Finder.html
├── InterceptableRouter.html
├── Logger.html
├── PostRoutingTask.html
├── Router.html
├── RoutingInterceptable.html
├── RoutingInterceptor.html
├── SimpleContainerFactory.html
├── StackIteratingFinder.html
├── StackIterator.html
├── StackPresentationHandler.html
└── WindowProvider.html
├── Steps.html
├── Structs
├── ActionConnectingAssembly.html
├── CATransactionWrappedAction.html
├── CATransactionWrappedContainerAction.html
├── ChildCoordinator.html
├── ClassFactory.html
├── ClassFinder.html
├── ClassWithContextFinder.html
├── CompleteFactory.html
├── ContainerStepChainAssembly.html
├── ContextSettingTask.html
├── CustomWindowProvider.html
├── DefaultContainerAdapterLocator.html
├── DefaultLogger.html
├── DefaultLogger
│ └── LogLevel.html
├── DefaultRouter.html
├── DefaultStackIterator.html
├── DefaultStackIterator
│ └── StartingPoint.html
├── DefaultStackPresentationHandler.html
├── Destination.html
├── DestinationStep.html
├── DetailsNavigationFinder.html
├── DismissalMethodProvidingContextTask.html
├── DispatchQueueWrappedAction.html
├── DispatchQueueWrappedContainerAction.html
├── FinderFactory.html
├── GlobalInterceptorRouter.html
├── InlineContextTask.html
├── InlineFactory.html
├── InlineInterceptor.html
├── InlinePostTask.html
├── InlineStackIteratingFinder.html
├── InstanceFinder.html
├── KeyWindowProvider.html
├── LastStepInChainAssembly.html
├── NavigationControllerAdapter.html
├── NavigationControllerFactory.html
├── NavigationDelayingInterceptor.html
├── NavigationDelayingInterceptor
│ └── Strategy.html
├── NilFactory.html
├── NilFinder.html
├── PresentingFinder.html
├── PresentingFinder
│ └── StartingPoint.html
├── SearchOptions.html
├── SingleNavigationRouter.html
├── SplitControllerAdapter.html
├── SplitControllerFactory.html
├── StepChainAssembly.html
├── StoryboardFactory.html
├── TabBarControllerAdapter.html
├── TabBarControllerFactory.html
├── UIHostingControllerFactory.html
├── UIHostingControllerWithContextFactory.html
└── UIHostingControllerWithContextFinder.html
├── Tasks.html
├── UIViewController's protocols.html
├── badge.svg
├── code_of_conduct.html
├── contributing.html
├── css
├── highlight.css
└── jazzy.css
├── docsets
├── RouteComposer.docset
│ └── Contents
│ │ ├── Info.plist
│ │ └── Resources
│ │ ├── Documents
│ │ ├── Additional Assemblies.html
│ │ ├── Assemblies.html
│ │ ├── Classes
│ │ │ ├── ActionToStepIntegrator.html
│ │ │ ├── CompleteFactoryAssembly.html
│ │ │ ├── CompleteFactoryChainAssembly.html
│ │ │ ├── GenericStepAssembly.html
│ │ │ ├── InlineContextTransformer.html
│ │ │ ├── NavigationControllerStep.html
│ │ │ ├── RouteComposerDefaults.html
│ │ │ ├── SingleContainerStep.html
│ │ │ ├── SingleNavigationLock.html
│ │ │ ├── SingleStep.html
│ │ │ ├── SplitControllerStep.html
│ │ │ ├── StepAssembly.html
│ │ │ ├── SwitchAssembly.html
│ │ │ └── TabBarControllerStep.html
│ │ ├── Core Entities.html
│ │ ├── Enums
│ │ │ ├── ChainAssembly.html
│ │ │ ├── GeneralAction.html
│ │ │ ├── GeneralStep.html
│ │ │ ├── LogMessage.html
│ │ │ ├── NavigationControllerActions.html
│ │ │ ├── NavigationControllerActions
│ │ │ │ ├── PushAction.html
│ │ │ │ ├── PushAsRootAction.html
│ │ │ │ └── PushReplacingLastAction.html
│ │ │ ├── RoutingError.html
│ │ │ ├── RoutingError
│ │ │ │ ├── Context.html
│ │ │ │ └── InitialControllerErrorState.html
│ │ │ ├── RoutingResult.html
│ │ │ ├── SplitViewControllerActions.html
│ │ │ ├── SplitViewControllerActions
│ │ │ │ ├── PushOnToDetailsAction.html
│ │ │ │ ├── PushToDetailsAction.html
│ │ │ │ └── SetAsMasterAction.html
│ │ │ ├── TabBarControllerActions.html
│ │ │ ├── TabBarControllerActions
│ │ │ │ └── AddTabAction.html
│ │ │ ├── ViewControllerActions.html
│ │ │ └── ViewControllerActions
│ │ │ │ ├── NilAction.html
│ │ │ │ ├── PresentModallyAction.html
│ │ │ │ ├── PresentModallyAction
│ │ │ │ └── ModalPresentationStartingPoint.html
│ │ │ │ └── ReplaceRootAction.html
│ │ ├── Extensions
│ │ │ ├── Array.html
│ │ │ ├── CATransaction.html
│ │ │ ├── DispatchQueue.html
│ │ │ ├── UIHostingController.html
│ │ │ ├── UINavigationController.html
│ │ │ ├── UISplitViewController.html
│ │ │ ├── UITabBarController.html
│ │ │ ├── UIViewController.html
│ │ │ └── UIWindow.html
│ │ ├── Extras.html
│ │ ├── Factories.html
│ │ ├── Finders.html
│ │ ├── General Actions.html
│ │ ├── Logging.html
│ │ ├── Other Classes.html
│ │ ├── Other Enums.html
│ │ ├── Other Extensions.html
│ │ ├── Other Guides.html
│ │ ├── Other Protocols.html
│ │ ├── Other Structs.html
│ │ ├── Protocols
│ │ │ ├── AbstractAction.html
│ │ │ ├── AbstractFactory.html
│ │ │ ├── ConcreteContainerAdapter.html
│ │ │ ├── ContainerAction.html
│ │ │ ├── ContainerAdapter.html
│ │ │ ├── ContainerAdapterLocator.html
│ │ │ ├── ContainerFactory.html
│ │ │ ├── ContainerViewController.html
│ │ │ ├── ContextAccepting.html
│ │ │ ├── ContextAcceptingView.html
│ │ │ ├── ContextChecking.html
│ │ │ ├── ContextInstantiatable.html
│ │ │ ├── ContextTask.html
│ │ │ ├── ContextTransformer.html
│ │ │ ├── CustomContainerViewController.html
│ │ │ ├── Dismissible.html
│ │ │ ├── DismissibleWithRuntimeStorage.html
│ │ │ ├── Factory.html
│ │ │ ├── Finder.html
│ │ │ ├── InterceptableRouter.html
│ │ │ ├── Logger.html
│ │ │ ├── PostRoutingTask.html
│ │ │ ├── Router.html
│ │ │ ├── RoutingInterceptable.html
│ │ │ ├── RoutingInterceptor.html
│ │ │ ├── SimpleContainerFactory.html
│ │ │ ├── StackIteratingFinder.html
│ │ │ ├── StackIterator.html
│ │ │ ├── StackPresentationHandler.html
│ │ │ └── WindowProvider.html
│ │ ├── Steps.html
│ │ ├── Structs
│ │ │ ├── ActionConnectingAssembly.html
│ │ │ ├── CATransactionWrappedAction.html
│ │ │ ├── CATransactionWrappedContainerAction.html
│ │ │ ├── ChildCoordinator.html
│ │ │ ├── ClassFactory.html
│ │ │ ├── ClassFinder.html
│ │ │ ├── ClassWithContextFinder.html
│ │ │ ├── CompleteFactory.html
│ │ │ ├── ContainerStepChainAssembly.html
│ │ │ ├── ContextSettingTask.html
│ │ │ ├── CustomWindowProvider.html
│ │ │ ├── DefaultContainerAdapterLocator.html
│ │ │ ├── DefaultLogger.html
│ │ │ ├── DefaultLogger
│ │ │ │ └── LogLevel.html
│ │ │ ├── DefaultRouter.html
│ │ │ ├── DefaultStackIterator.html
│ │ │ ├── DefaultStackIterator
│ │ │ │ └── StartingPoint.html
│ │ │ ├── DefaultStackPresentationHandler.html
│ │ │ ├── Destination.html
│ │ │ ├── DestinationStep.html
│ │ │ ├── DetailsNavigationFinder.html
│ │ │ ├── DismissalMethodProvidingContextTask.html
│ │ │ ├── DispatchQueueWrappedAction.html
│ │ │ ├── DispatchQueueWrappedContainerAction.html
│ │ │ ├── FinderFactory.html
│ │ │ ├── GlobalInterceptorRouter.html
│ │ │ ├── InlineContextTask.html
│ │ │ ├── InlineFactory.html
│ │ │ ├── InlineInterceptor.html
│ │ │ ├── InlinePostTask.html
│ │ │ ├── InlineStackIteratingFinder.html
│ │ │ ├── InstanceFinder.html
│ │ │ ├── KeyWindowProvider.html
│ │ │ ├── LastStepInChainAssembly.html
│ │ │ ├── NavigationControllerAdapter.html
│ │ │ ├── NavigationControllerFactory.html
│ │ │ ├── NavigationDelayingInterceptor.html
│ │ │ ├── NavigationDelayingInterceptor
│ │ │ │ └── Strategy.html
│ │ │ ├── NilFactory.html
│ │ │ ├── NilFinder.html
│ │ │ ├── PresentingFinder.html
│ │ │ ├── PresentingFinder
│ │ │ │ └── StartingPoint.html
│ │ │ ├── SearchOptions.html
│ │ │ ├── SingleNavigationRouter.html
│ │ │ ├── SplitControllerAdapter.html
│ │ │ ├── SplitControllerFactory.html
│ │ │ ├── StepChainAssembly.html
│ │ │ ├── StoryboardFactory.html
│ │ │ ├── TabBarControllerAdapter.html
│ │ │ ├── TabBarControllerFactory.html
│ │ │ ├── UIHostingControllerFactory.html
│ │ │ ├── UIHostingControllerWithContextFactory.html
│ │ │ └── UIHostingControllerWithContextFinder.html
│ │ ├── Tasks.html
│ │ ├── UIViewController's protocols.html
│ │ ├── code_of_conduct.html
│ │ ├── contributing.html
│ │ ├── css
│ │ │ ├── highlight.css
│ │ │ └── jazzy.css
│ │ ├── examples.html
│ │ ├── img
│ │ │ ├── carat.png
│ │ │ ├── dash.png
│ │ │ └── spinner.gif
│ │ ├── index.html
│ │ ├── js
│ │ │ ├── jazzy.js
│ │ │ ├── jazzy.search.js
│ │ │ ├── jquery.min.js
│ │ │ ├── lunr.min.js
│ │ │ └── typeahead.jquery.js
│ │ ├── readme.html
│ │ └── search.json
│ │ └── docSet.dsidx
└── RouteComposer.tgz
├── examples.html
├── img
├── carat.png
├── dash.png
└── spinner.gif
├── index.html
├── js
├── jazzy.js
├── jazzy.search.js
├── jquery.min.js
├── lunr.min.js
└── typeahead.jquery.js
├── readme.html
├── search.json
├── tests
├── AbstractFactory.swift.html
├── Action.swift.html
├── ActionBox.swift.html
├── ActionConnectingAssembly.swift.html
├── ActionToStepIntegrator.swift.html
├── AnyContextBox.swift.html
├── AnyFactoryBox.swift.html
├── Array+Extension.swift.html
├── Array+PrivateExtension.swift.html
├── BaseEntitiesCollector.swift.html
├── BaseStep.swift.html
├── CATransaction+Action.swift.html
├── ChainAssembly.swift.html
├── ChildCoordinator.swift.html
├── ClassFactory.swift.html
├── ClassFinder.swift.html
├── ClassWithContextFinder.swift.html
├── CompleteFactory.swift.html
├── CompleteFactoryAssembly.swift.html
├── CompleteFactoryChainAssembly.swift.html
├── ContainerActionBox.swift.html
├── ContainerAdapter.swift.html
├── ContainerFactory.swift.html
├── ContainerFactoryBox.swift.html
├── ContainerStepChainAssembly.swift.html
├── ContextAccepting.swift.html
├── ContextAcceptingView.swift.html
├── ContextInstantiatable.swift.html
├── ContextSettingTask.swift.html
├── ContextTask.swift.html
├── ContextTaskBox.swift.html
├── ContextTaskMultiplexer.swift.html
├── ContextTransformerBox.swift.html
├── ConvertingStep.swift.html
├── CustomWindowProvider.swift.html
├── DefaultContainerAdapterLocator.swift.html
├── DefaultLogger.swift.html
├── DefaultRouter+Extension.swift.html
├── DefaultRouter.swift.html
├── DefaultStackIterator.swift.html
├── DefaultStackPresentationHandler.swift.html
├── Destination.swift.html
├── DestinationStep.swift.html
├── DetailsNavigationFinder.swift.html
├── DismissalMethodProvidingContextTask.swift.html
├── Dismissible.swift.html
├── DispatchQueue+Action.swift.html
├── Factory.swift.html
├── FactoryBox.swift.html
├── Finder.swift.html
├── FinderBox.swift.html
├── FinderFactory.swift.html
├── GeneralStep.swift.html
├── GenericStepAssembly.swift.html
├── GlobalInterceptorRouter.swift.html
├── InPlaceTransformingAnyContext.swift.html
├── InlineContextTask.swift.html
├── InlineContextTransformer.swift.html
├── InlineFactory.swift.html
├── InlineInterceptor.swift.html
├── InlinePostTask.swift.html
├── InlineStackIteratingFinder.swift.html
├── InstanceFinder.swift.html
├── InterceptorMultiplexer.swift.html
├── KeyWindowProvider.swift.html
├── LastStepInChainAssembly.swift.html
├── MainThreadChecking.swift.html
├── NavigationController+Extension.swift.html
├── NavigationControllerAdapter.swift.html
├── NavigationControllerFactory.swift.html
├── NavigationControllerStep.swift.html
├── NavigationDelayInterceptor.swift.html
├── NilContextTransformer.swift.html
├── NilFactory.swift.html
├── NilFinder.swift.html
├── PostRoutingTask.swift.html
├── PostRoutingTaskBox.swift.html
├── PostRoutingTaskMultiplexer.swift.html
├── PostponedIntegrationFactory.swift.html
├── PreparableEntity.swift.html
├── PresentingFinder.swift.html
├── RouteComposerDefaults.swift.html
├── Router+Destination.swift.html
├── Router.swift.html
├── RoutingError.swift.html
├── RoutingInterceptable.swift.html
├── RoutingInterceptor.swift.html
├── RoutingInterceptorBox.swift.html
├── RoutingResult.swift.html
├── SearchOptions.swift.html
├── SimpleContainerFactory.swift.html
├── SingleContainerStep.swift.html
├── SingleNavigationRouter.swift.html
├── SingleStep.swift.html
├── SplitControllerAdapter.swift.html
├── SplitControllerFactory.swift.html
├── SplitControllerStep.swift.html
├── SplitViewController+Extension.swift.html
├── StackIteratingFinder.swift.html
├── StepAssembly.swift.html
├── StepChainAssembly.swift.html
├── StoryboardFactory.swift.html
├── SwitchAssembly.swift.html
├── SwitcherStep.swift.html
├── TabBarControllerAdapter.swift.html
├── TabBarControllerFactory.swift.html
├── TabBarControllerStep.swift.html
├── TabBarViewController+Extension.swift.html
├── TaskCollector.swift.html
├── UIHostingControllerFactory.swift.html
├── UIHostingControllerWithContextFactory.swift.html
├── UIHostingControllerWithContextFinder.swift.html
├── UINavigationController+Action.swift.html
├── UISplitViewController+Action.swift.html
├── UITabBarController+Action.swift.html
├── UIViewController+Action.swift.html
├── UIViewController+Extension.swift.html
├── UIViewController+PrivateExtension.swift.html
├── UIWindow+Extension.swift.html
├── highlight.pack.js
├── index.html
├── list.min.js
├── logo.jpg
└── slather.css
└── undocumented.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: ekazaev
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **OR**
21 |
22 | 1. Create the configuration '...' and apply it to the `Router`
23 | 2. See incorrect '....' behaviour
24 | 3. Expected '....' behaviour
25 |
26 | *NB: A picture is worth a thousand words. So it is always better to provide a sample project that contains and clearly shows the issue*
27 |
28 | **Expected behavior**
29 | A clear and concise description of what you expected to happen.
30 |
31 | **Configuration:**
32 | - Device: [e.g. iPhone11]
33 | - OS: [e.g. iOS13.5]
34 | - RouteComposer version [e.g. 2.6.0]
35 |
36 | **Additional context**
37 | Add any other context about the problem here.
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
26 | # Carthage/Checkouts
27 |
28 | Carthage/Build
29 |
30 | # We recommend against adding the Pods directory to your .gitignore. However
31 | # you should judge for yourself, the pros and cons are mentioned at:
32 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
33 | #
34 | # Note: if you ignore the Pods directory, make sure to uncomment
35 | # `pod install` in .travis.yml
36 | #
37 | # Pods/
38 | Gemfile.lock
39 |
--------------------------------------------------------------------------------
/.slather.yml:
--------------------------------------------------------------------------------
1 | xcodeproj: ./Example/RouteComposer.xcodeproj
2 | workspace: ./Example/RouteComposer.xcworkspace
3 | scheme: RouteComposer-Example
4 | source_directory: ./RouteComposer/Classes
5 | binary_basename: RouteComposer
6 | ignore:
7 | - ./Example/*
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | --allman false
2 | --binarygrouping none
3 | --closingparen balanced
4 | --commas inline
5 | --conflictmarkers reject
6 | --decimalgrouping none
7 | --elseposition same-line
8 | --guardelse same-line
9 | --empty void
10 | --exponentcase lowercase
11 | --exponentgrouping disabled
12 | --fractiongrouping disabled
13 | --fragment false
14 | --header "\nRouteComposer\n{file}\nhttps://github.com/ekazaev/route-composer\n\nCreated by Eugene Kazaev in 2018-{year}.\nDistributed under the MIT license.\n\nBecome a sponsor:\nhttps://github.com/sponsors/ekazaev\n"
15 | --hexgrouping none
16 | --hexliteralcase uppercase
17 | --ifdef no-indent
18 | --importgrouping alphabetized
19 | --indent 4
20 | --indentcase false
21 | --linebreaks lf
22 | --maxwidth none
23 | --nospaceoperators ...,..<,..>
24 | --octalgrouping none
25 | --operatorfunc spaced
26 | --patternlet hoist
27 | --self init-only
28 | --selfrequired
29 | --semicolons inline
30 | --stripunusedargs closure-only
31 | --tabwidth unspecified
32 | --trailingclosures
33 | --trimwhitespace always
34 | --wraparguments preserve
35 | --closingparen same-line
36 | --wrapcollections preserve
37 | --xcodeindentation enabled
38 | --modifierorder public,final,override
39 | --disable blankLinesAtEndOfScope,blankLinesAtStartOfScope,redundantReturn,wrapMultilineStatementBraces
40 | --enable isEmpty
41 | --exclude Pods,docs,Example/Pods
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | ./Example/.swiftlint.yml
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * http://www.objc.io/issue-6/travis-ci.html
3 | # * https://github.com/supermarin/xcpretty#usage
4 | # * slather coverage --html --output-directory ./docs/tests --arch x86_64
5 |
6 | osx_image: xcode12.2
7 | language: swift
8 | cache: cocoapods, slather
9 | podfile: Example/Podfile
10 | before_install:
11 | - gem install slather
12 | - gem install cocoapods # Since Travis is not always on latest version
13 | - pod install --project-directory=Example
14 | script:
15 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/RouteComposer.xcworkspace -scheme RouteComposer-Example -sdk iphonesimulator -derivedDataPath ${TRAVIS_BUILD_DIR}/myDerivedData -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.2' ONLY_ACTIVE_ARCH=YES | xcpretty
16 | - pod lib lint
17 | after_success:
18 | - slather coverage -t -b ${TRAVIS_BUILD_DIR}/myDerivedData --cobertura-xml --output-directory ${TRAVIS_BUILD_DIR}/tests --arch x86_64 --verbose
19 | - bash <(curl -s https://codecov.io/bash) -f ${TRAVIS_BUILD_DIR}/tests/cobertura.xml -X coveragepy -X gcov -X xcode -t 9438422d-c067-4351-9e4f-f560c3ffbfa8
20 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to RouteComposer
2 |
3 | We welcome contributions to the RouteComposer's repository.
4 |
5 | Contributing can take many forms, but to make administering the process easier, we handle all contributions in one of two ways:
6 |
7 | ### Making Suggestions & Pointing Out Problems
8 |
9 | Such contributions include:
10 |
11 | - Reporting bugs
12 | - Sending feature requests
13 | - Pointing out typos
14 |
15 | For these types of contributions, **please create a [GitHub Issue](https://guides.github.com/features/issues/) in the appropriate repo** containing the message that you'd like to convey.
16 |
17 | ### Improving the Codebase
18 |
19 | This would include:
20 |
21 | - Fixing bugs
22 | - Tweaking project file settings
23 | - Writing unit tests
24 | - Submitting performance and other code improvements
25 | - Adding new features
26 | - Correct typos
27 |
28 | To contribute in this way, **please [submit a pull request](https://help.github.com/articles/using-pull-requests/)** by:
29 |
30 | 1. [Forking the repo](https://help.github.com/articles/fork-a-repo/) to which you'd like to submit a contribution
31 | 2. Committing changes to your fork
32 | 3. Pushing your changes to GitHub
33 | 4. Submitting a pull request from your changes
34 |
35 | If your pull request is related to something already tracked in a GitHub Issue, please be sure to note the issue number (and include a link to the issue page) in your pull request.
36 |
--------------------------------------------------------------------------------
/Example/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /.idea/shelf/
3 | /.idea/workspace.xml
4 |
5 | # Datasource local storage ignored files
6 | /.idea/dataSources/
7 | dataSources.local.xml
8 |
9 | # Editor-based HTTP Client requests
10 | /.idea/httpRequests/
11 | rest-client.private.env.json
12 | http-client.private.env.json
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '15.0'
2 | use_frameworks!
3 |
4 | plugin 'slather'
5 |
6 | target 'RouteComposer_Example' do
7 | pod 'RouteComposer', :path => '../'
8 | pod 'SwiftFormat/CLI', '0.43.5'
9 |
10 | target 'RouteComposer_Tests' do
11 | pod 'RouteComposer', :path => '../'
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - RouteComposer (2.20.0)
3 | - SwiftFormat/CLI (0.43.5)
4 |
5 | DEPENDENCIES:
6 | - RouteComposer (from `../`)
7 | - SwiftFormat/CLI (= 0.43.5)
8 |
9 | SPEC REPOS:
10 | trunk:
11 | - SwiftFormat
12 |
13 | EXTERNAL SOURCES:
14 | RouteComposer:
15 | :path: "../"
16 |
17 | SPEC CHECKSUMS:
18 | RouteComposer: ad303f06f7fb625b1ec890c4968c2ce07d16b2a8
19 | SwiftFormat: 352ea545e3e13cfd7a449e621c1f3c2e244d4906
20 |
21 | PODFILE CHECKSUM: 60fdfd081ebcb6e88fc9f1261e0b0e989ba665c8
22 |
23 | COCOAPODS: 1.13.0
24 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/DeepLinkLibrary.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RouteComposer",
3 | "version": "0.9",
4 | "summary": "Standalone UIViewController's routing library.",
5 | "description": "TODO: Add long description of the pod here.",
6 | "homepage": "https://github.com/saksdirect/RouteComposer",
7 | "license": {
8 | "type": "MIT",
9 | "file": "LICENSE"
10 | },
11 | "authors": {
12 | "Evgeny Kazaev": "ekazaev@gilt.com"
13 | },
14 | "source": {
15 | "git": "https://github.com/saksdirect/RouteComposer.git",
16 | "tag": "0.9"
17 | },
18 | "platforms": {
19 | "ios": "9.0"
20 | },
21 | "source_files": "RouteComposer/Classes/**/*",
22 | "frameworks": "UIKit"
23 | }
24 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/RouteComposer.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RouteComposer",
3 | "version": "2.20.0",
4 | "summary": "Protocol oriented library that helps to handle view controllers composition, navigation and deep linking tasks.",
5 | "swift_versions": "6.1",
6 | "description": "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. Can be used as the universal replacement for the Coordinator pattern.",
7 | "homepage": "https://github.com/ekazaev/route-composer",
8 | "license": {
9 | "type": "MIT",
10 | "file": "LICENSE"
11 | },
12 | "authors": {
13 | "Evgeny Kazaev": "eugene.kazaev@gmail.com"
14 | },
15 | "source": {
16 | "git": "https://github.com/ekazaev/route-composer.git",
17 | "tag": "2.20.0"
18 | },
19 | "platforms": {
20 | "ios": "15.0"
21 | },
22 | "source_files": "RouteComposer/Classes/**/*.*",
23 | "frameworks": "UIKit",
24 | "swift_version": "6.1"
25 | }
26 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - RouteComposer (2.20.0)
3 | - SwiftFormat/CLI (0.43.5)
4 |
5 | DEPENDENCIES:
6 | - RouteComposer (from `../`)
7 | - SwiftFormat/CLI (= 0.43.5)
8 |
9 | SPEC REPOS:
10 | trunk:
11 | - SwiftFormat
12 |
13 | EXTERNAL SOURCES:
14 | RouteComposer:
15 | :path: "../"
16 |
17 | SPEC CHECKSUMS:
18 | RouteComposer: ad303f06f7fb625b1ec890c4968c2ce07d16b2a8
19 | SwiftFormat: 352ea545e3e13cfd7a449e621c1f3c2e244d4906
20 |
21 | PODFILE CHECKSUM: 60fdfd081ebcb6e88fc9f1261e0b0e989ba665c8
22 |
23 | COCOAPODS: 1.13.0
24 |
--------------------------------------------------------------------------------
/Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Pods/SwiftFormat/CommandLineTool/swiftformat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/Pods/SwiftFormat/CommandLineTool/swiftformat
--------------------------------------------------------------------------------
/Example/Pods/SwiftFormat/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Nick Lockwood
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example-RouteComposer_Tests/Pods-RouteComposer_Example-RouteComposer_Tests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | ${PODS_DEVELOPMENT_LANGUAGE}
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example-RouteComposer_Tests/Pods-RouteComposer_Example-RouteComposer_Tests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_RouteComposer_Example_RouteComposer_Tests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_RouteComposer_Example_RouteComposer_Tests
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example-RouteComposer_Tests/Pods-RouteComposer_Example-RouteComposer_Tests-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_RouteComposer_Example_RouteComposer_TestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_RouteComposer_Example_RouteComposer_TestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example-RouteComposer_Tests/Pods-RouteComposer_Example-RouteComposer_Tests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer/RouteComposer.framework/Headers"
6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks'
7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
8 | OTHER_LDFLAGS = $(inherited) -framework "RouteComposer" -framework "UIKit"
9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
10 | PODS_BUILD_DIR = ${BUILD_DIR}
11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
13 | PODS_ROOT = ${SRCROOT}/Pods
14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example-RouteComposer_Tests/Pods-RouteComposer_Example-RouteComposer_Tests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_RouteComposer_Example_RouteComposer_Tests {
2 | umbrella header "Pods-RouteComposer_Example-RouteComposer_Tests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example-RouteComposer_Tests/Pods-RouteComposer_Example-RouteComposer_Tests.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer/RouteComposer.framework/Headers"
6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks'
7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
8 | OTHER_LDFLAGS = $(inherited) -framework "RouteComposer" -framework "UIKit"
9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
10 | PODS_BUILD_DIR = ${BUILD_DIR}
11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
13 | PODS_ROOT = ${SRCROOT}/Pods
14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example/Pods-RouteComposer_Example-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | ${PODS_DEVELOPMENT_LANGUAGE}
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example/Pods-RouteComposer_Example-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_RouteComposer_Example : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_RouteComposer_Example
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example/Pods-RouteComposer_Example-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_RouteComposer_ExampleVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_RouteComposer_ExampleVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example/Pods-RouteComposer_Example.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer/RouteComposer.framework/Headers"
6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
8 | OTHER_LDFLAGS = $(inherited) -framework "RouteComposer" -framework "UIKit"
9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
10 | PODS_BUILD_DIR = ${BUILD_DIR}
11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
13 | PODS_ROOT = ${SRCROOT}/Pods
14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example/Pods-RouteComposer_Example.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_RouteComposer_Example {
2 | umbrella header "Pods-RouteComposer_Example-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-RouteComposer_Example/Pods-RouteComposer_Example.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer/RouteComposer.framework/Headers"
6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
8 | OTHER_LDFLAGS = $(inherited) -framework "RouteComposer" -framework "UIKit"
9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
10 | PODS_BUILD_DIR = ${BUILD_DIR}
11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
13 | PODS_ROOT = ${SRCROOT}/Pods
14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | ${PODS_DEVELOPMENT_LANGUAGE}
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 2.20.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_RouteComposer : NSObject
3 | @end
4 | @implementation PodsDummy_RouteComposer
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double RouteComposerVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char RouteComposerVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_LDFLAGS = $(inherited) -framework "UIKit"
6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer.modulemap:
--------------------------------------------------------------------------------
1 | framework module RouteComposer {
2 | umbrella header "RouteComposer-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_LDFLAGS = $(inherited) -framework "UIKit"
6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/RouteComposer/RouteComposer.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RouteComposer
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_LDFLAGS = $(inherited) -framework "UIKit"
4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
9 | PODS_ROOT = ${SRCROOT}
10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat
11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
13 | SKIP_INSTALL = YES
14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
15 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
9 | PODS_ROOT = ${SRCROOT}
10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat
11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
13 | SKIP_INSTALL = YES
14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
15 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | PODS_BUILD_DIR = ${BUILD_DIR}
4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
5 | PODS_ROOT = ${SRCROOT}
6 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat
7 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
8 | SKIP_INSTALL = YES
9 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
10 |
--------------------------------------------------------------------------------
/Example/RouteComposer.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/RouteComposer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/RouteComposer.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/RouteComposer.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/RouteComposer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Example/RouteComposer/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AppDelegate.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import os.log
14 | import RouteComposer
15 | import UIKit
16 |
17 | @main
18 | class AppDelegate: UIResponder, UIApplicationDelegate {
19 |
20 | var window: UIWindow?
21 |
22 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
23 | RouteComposerDefaults.configureWith(logger: DefaultLogger(.verbose, osLog: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Router")))
24 | ConfigurationHolder.configuration = ExampleConfiguration()
25 |
26 | // Try in mobile Safari to test the deep linking to the app:
27 | // Try it when you are on any screen in the app to check that you will always land where you have to be
28 | // depending on the configuration provided.
29 | //
30 | // dll://colors?color=AABBCC
31 | // dll://products?product=01
32 | // dll://cities?city=01
33 | ExampleUniversalLinksManager.configure()
34 | return true
35 | }
36 |
37 | func application(_ application: UIApplication,
38 | open url: URL,
39 | sourceApplication: String?,
40 | annotation: Any) -> Bool {
41 | guard let destination = ExampleUniversalLinksManager.destination(for: url) else {
42 | return false
43 | }
44 |
45 | do {
46 | try UIViewController.router.navigate(to: destination)
47 | return true
48 | } catch {
49 | return false
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Configuration/BlurredBackgroundPresentationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // BlurredBackgroundPresentationController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | class BlurredBackgroundPresentationController: UIPresentationController {
17 |
18 | override var shouldRemovePresentersView: Bool {
19 | true
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Configuration/BlurredBackgroundTransitionController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // BlurredBackgroundTransitionController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | enum BlurredBackgroundTransitionType {
17 | case present
18 | case dismiss
19 | }
20 |
21 | class BlurredBackgroundTransitionController: NSObject, UIViewControllerTransitioningDelegate {
22 |
23 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
24 | BlurredBackgroundTransitionAnimator(transitionType: .present)
25 | }
26 |
27 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
28 | BlurredBackgroundTransitionAnimator(transitionType: .dismiss)
29 | }
30 |
31 | func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
32 | BlurredBackgroundPresentationController(presentedViewController: presented, presenting: presenting)
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Configuration/ExampleAnalyticsSupport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ExampleAnalyticsSupport.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol ExampleAnalyticsSupport {
18 |
19 | var screenType: ExampleScreenTypes { get }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Configuration/ExampleGenericContextTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ExampleGenericContextTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | struct ExampleGenericContextTask: ContextTask {
18 |
19 | func perform(on viewController: VC, with context: C) throws {
20 | print("View controller name is \(String(describing: viewController))")
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Configuration/ExampleScreenTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ExampleScreenTypes.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 |
16 | // Enum to demo analytics
17 | enum ExampleScreenTypes {
18 |
19 | case circle
20 |
21 | case square
22 |
23 | case home
24 |
25 | case color
26 |
27 | case ruleSupport
28 |
29 | case empty
30 |
31 | case star
32 |
33 | case product
34 |
35 | case split
36 |
37 | case citiesList
38 |
39 | case cityDetail
40 |
41 | case collections
42 |
43 | case favorites
44 |
45 | case login
46 |
47 | case secondLevelModal
48 |
49 | case welcome
50 |
51 | case appLink
52 | }
53 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Extensions/Router+Destination.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // Router+Destination.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | /// Simple extension to support `Destination` instance directly by the `Router`.
18 | @MainActor
19 | extension Router {
20 |
21 | func navigate(to step: DestinationStep, with context: Context) throws {
22 | try navigate(to: step, with: context, animated: true, completion: nil)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Extensions/UIColor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // UIColor.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | extension UIColor {
16 |
17 | convenience init(hexString: String) {
18 | if let rgbValue = UInt(hexString, radix: 16) {
19 | let red = CGFloat((rgbValue >> 16) & 0xFF) / 255
20 | let green = CGFloat((rgbValue >> 8) & 0xFF) / 255
21 | let blue = CGFloat(rgbValue & 0xFF) / 255
22 | self.init(red: red, green: green, blue: blue, alpha: 1.0)
23 | } else {
24 | self.init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Extensions/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import RouteComposer
14 | import UIKit
15 |
16 | extension UIViewController {
17 |
18 | // This class is needed just for the test purposes
19 | @MainActor
20 | private final class TestInterceptor: RoutingInterceptor {
21 | let logger: RouteComposer.Logger?
22 | let message: String
23 |
24 | init(_ message: String) {
25 | self.logger = RouteComposerDefaults.shared.logger
26 | self.message = message
27 | }
28 |
29 | func perform(with context: Any?, completion: @escaping (RoutingResult) -> Void) {
30 | logger?.log(.info(message))
31 | completion(.success)
32 | }
33 | }
34 |
35 | @MainActor
36 | static let router: Router = {
37 | let libRouter = DefaultRouter()
38 | let failingRouter = FailingRouter(router: libRouter)
39 | var defaultRouter = GlobalInterceptorRouter(router: failingRouter)
40 | defaultRouter.addGlobal(TestInterceptor("Global interceptors start"))
41 | defaultRouter.addGlobal(NavigationDelayingInterceptor(strategy: .wait))
42 | defaultRouter.add(TestInterceptor("Router interceptors start"))
43 | return AnalyticsRouterDecorator(router: defaultRouter)
44 | }()
45 |
46 | var router: Router {
47 | UIViewController.router
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-AppStore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-AppStore.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPad76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPad76.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPad76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPad76@2x.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPad83@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPad83@2x.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPhone60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPhone60@2x.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPhone60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/AppIcon.appiconset/icon-iPhone60@3x.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/first.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "first.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/first.imageset/first.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/first.imageset/first.pdf
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/second.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "second.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/second.imageset/second.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/second.imageset/second.pdf
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/star.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "star.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "star@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "star@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/star.imageset/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/star.imageset/star.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/star.imageset/star@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/star.imageset/star@2x.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Images.xcassets/star.imageset/star@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/Example/RouteComposer/Images.xcassets/star.imageset/star@3x.png
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ContainerViewController/ContainerViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ContainerViewController/ContainerViewController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ContainerViewController/ContainerViewController/ContainerViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ContainerViewController.h
3 | // ContainerViewController
4 | //
5 | // Created by Eugene Kazaev on 29/08/2018.
6 | // Copyright © 2018 RouteComposer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ContainerViewController.
12 | FOUNDATION_EXPORT double ContainerViewControllerVersionNumber;
13 |
14 | //! Project version string for ContainerViewController.
15 | FOUNDATION_EXPORT const unsigned char ContainerViewControllerVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ContainerViewController/ContainerViewController/CustomViewControllerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // CustomViewControllerDelegate.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | public protocol CustomViewControllerDelegate: AnyObject {
17 |
18 | func dismissCustomContainer(controller: CustomContainerController)
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ContainerViewController/ContainerViewController/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImageDetailsController/ImageDetailsController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImageDetailsController/ImageDetailsController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImageDetailsController/ImageDetailsController/ImageDetailsController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImageDetailsController.h
3 | // ImageDetailsController
4 | //
5 | // Created by Eugene Kazaev on 29/08/2018.
6 | // Copyright © 2018 RouteComposer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ImageDetailsController.
12 | FOUNDATION_EXPORT double ImageDetailsControllerVersionNumber;
13 |
14 | //! Project version string for ImageDetailsController.
15 | FOUNDATION_EXPORT const unsigned char ImageDetailsControllerVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImageDetailsController/ImageDetailsController/ImageDetailsControllerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImageDetailsControllerDelegate.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | public protocol ImageDetailsControllerDelegate: AnyObject {
17 |
18 | func dismiss(imageDetails: ImageDetailsViewController)
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImageDetailsController/ImageDetailsController/ImageDetailsFetcher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImageDetailsFetcher.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | public protocol ImageDetailsFetcher {
17 | func details(for imageID: String, completion: @escaping (_: UIImage?) -> Void)
18 | }
19 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImageDetailsController/ImageDetailsController/ImageDetailsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImageDetailsViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | public final class ImageDetailsViewController: UIViewController {
17 |
18 | @IBOutlet private var imageView: UIImageView!
19 |
20 | public weak var delegate: ImageDetailsControllerDelegate?
21 |
22 | public var imageFetcher: ImageDetailsFetcher?
23 |
24 | public var imageID: String? {
25 | didSet {
26 | reloadData()
27 | }
28 | }
29 |
30 | public override func viewDidLoad() {
31 | super.viewDidLoad()
32 | reloadData()
33 | }
34 |
35 | private func reloadData() {
36 | guard isViewLoaded, let imageID else {
37 | return
38 | }
39 | view.accessibilityIdentifier = "image\(imageID)ViewController"
40 |
41 | imageFetcher?.details(for: imageID) { image in
42 | self.imageView.image = image
43 | }
44 | }
45 |
46 | @IBAction func doneTapped() {
47 | delegate?.dismiss(imageDetails: self)
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImageDetailsController/ImageDetailsController/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImagesController/ImagesController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImagesController/ImagesController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImagesController/ImagesController/ImagesController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImagesController.h
3 | // ImagesController
4 | //
5 | // Created by Eugene Kazaev on 29/08/2018.
6 | // Copyright © 2018 RouteComposer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ImagesController.
12 | FOUNDATION_EXPORT double ImagesControllerVersionNumber;
13 |
14 | //! Project version string for ImagesController.
15 | FOUNDATION_EXPORT const unsigned char ImagesControllerVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImagesController/ImagesController/ImagesControllerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImagesControllerDelegate.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | public protocol ImagesControllerDelegate: AnyObject {
17 |
18 | func didSelect(imageID: String, in controller: ImagesViewController)
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImagesController/ImagesController/ImagesFetcher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImagesFetcher.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | public protocol ImagesFetcher {
16 |
17 | func loadImages(completion: @escaping (_: [String]) -> Void)
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImagesController/ImagesController/ImagesViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImagesViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | public final class ImagesViewController: UITableViewController {
17 |
18 | public weak var delegate: ImagesControllerDelegate?
19 |
20 | public var imageFetcher: ImagesFetcher?
21 |
22 | private var imagesNames: [String] = []
23 |
24 | public override func viewDidLoad() {
25 | super.viewDidLoad()
26 | reloadData()
27 | }
28 |
29 | private func reloadData() {
30 | view.accessibilityIdentifier = "imagesViewController"
31 |
32 | imageFetcher?.loadImages { imagesNames in
33 | self.imagesNames = imagesNames
34 | self.tableView.reloadData()
35 | }
36 | }
37 |
38 | public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
39 | imagesNames.count
40 | }
41 |
42 | public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
43 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "ImageCell") else {
44 | fatalError("Unable to dequeue a reusable cell.")
45 | }
46 | cell.textLabel?.text = imagesNames[indexPath.row]
47 | return cell
48 | }
49 |
50 | public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
51 | delegate?.didSelect(imageID: imagesNames[indexPath.row], in: self)
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/Example/RouteComposer/Libraries/ImagesController/ImagesController/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/RouteComposer/URLTranslators/ColorURLTranslator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ColorURLTranslator.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class ColorURLTranslator: ExampleURLTranslator {
18 |
19 | func destination(from url: URL) -> AnyDestination? {
20 | guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false),
21 | let queryItems = urlComponents.queryItems,
22 | let colorItem = queryItems.first(where: { $0.name == "color" }),
23 | let colorValue = colorItem.value else {
24 | return nil
25 | }
26 |
27 | return Destination(to: ConfigurationHolder.configuration.colorScreen, with: colorValue).unwrapped()
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/Example/RouteComposer/URLTranslators/ExampleURLTranslator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ExampleURLTranslator.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | @MainActor
18 | protocol ExampleURLTranslator {
19 |
20 | func destination(from url: URL) -> AnyDestination?
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Example/RouteComposer/URLTranslators/ExampleUniversalLinksManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ExampleUniversalLinksManager.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | // Simplest universal link manager. You can use any library or your own implementation using the similar strategy
18 | // transforming data that is contained in the `URL` into `AnyDestination` instance.
19 | enum ExampleUniversalLinksManager {
20 |
21 | @MainActor
22 | private static var translators: [ExampleURLTranslator] = []
23 |
24 | @MainActor
25 | static func register(translator: ExampleURLTranslator) {
26 | translators.append(translator)
27 | }
28 |
29 | @MainActor
30 | static func destination(for url: URL) -> AnyDestination? {
31 | guard let translator = translators.first(where: { $0.destination(from: url) != nil }) else {
32 | return nil
33 | }
34 |
35 | return translator.destination(from: url)
36 | }
37 |
38 | }
39 |
40 | extension ExampleUniversalLinksManager {
41 |
42 | @MainActor
43 | static func configure() {
44 | ExampleUniversalLinksManager.register(translator: ColorURLTranslator())
45 | ExampleUniversalLinksManager.register(translator: ProductURLTranslator())
46 | ExampleUniversalLinksManager.register(translator: CityURLTranslator())
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/CircleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // CircleViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import RouteComposer
14 | import UIKit
15 |
16 | class CircleViewController: UIViewController, ExampleAnalyticsSupport {
17 |
18 | let screenType = ExampleScreenTypes.circle
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | title = "Circle"
23 | }
24 |
25 | @IBAction func goToSquareTapped() {
26 | try? router.navigate(to: ConfigurationHolder.configuration.squareScreen, with: nil)
27 | }
28 |
29 | @IBAction func goToRandomColorTapped() {
30 | try? router.navigate(to: ConfigurationHolder.configuration.colorScreen, with: "0000FF")
31 | }
32 |
33 | @IBAction func goToDeepModalTapped() {
34 | try? router.navigate(to: ConfigurationHolder.configuration.routingSupportScreen, with: "00FF00")
35 | }
36 |
37 | @IBAction func goToSuperModalTapped() {
38 | try? router.navigate(to: ConfigurationHolder.configuration.secondModalScreen, with: "0000FF")
39 | }
40 |
41 | @IBAction func goToProductTapped() {
42 | try? router.navigate(to: ProductConfiguration.productScreen, with: ProductContext(productId: "00"))
43 | }
44 |
45 | @IBAction func goToWelcomeTapped() {
46 | try? router.navigate(to: ConfigurationHolder.configuration.welcomeScreen, with: nil)
47 | }
48 |
49 | @IBAction func goToImagesTapped() {
50 | try? router.navigate(to: ImagesConfigurationWithLibrary.images())
51 | }
52 |
53 | @IBAction func goToImagesNoLibraryTapped() {
54 | ImagesWithoutLibraryConfiguration.shared.showCustomController()
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Cities/CityDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // CityDetailViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class CityDetailContextTask: ContextTask {
18 |
19 | func perform(on viewController: CityDetailViewController, with context: Int) throws {
20 | viewController.cityId = context
21 | }
22 |
23 | }
24 |
25 | class CityDetailViewController: UIViewController, ExampleAnalyticsSupport {
26 |
27 | let screenType = ExampleScreenTypes.cityDetail
28 |
29 | @IBOutlet private var detailsTextView: UITextView!
30 |
31 | var cityId: Int? {
32 | didSet {
33 | reloadData()
34 | }
35 | }
36 |
37 | override func viewDidLoad() {
38 | super.viewDidLoad()
39 | reloadData()
40 | }
41 |
42 | private func reloadData() {
43 | guard isViewLoaded, let city = CitiesDataModel.cities.first(where: { $0.cityId == cityId }) else {
44 | return
45 | }
46 | title = "\(city.city)"
47 |
48 | detailsTextView.text = city.city + "\n\n" + city.description
49 | if let cityId {
50 | view.accessibilityIdentifier = "cityDetailsViewController+\(cityId)"
51 | } else {
52 | view.accessibilityIdentifier = "cityDetailsViewController"
53 | }
54 | }
55 |
56 | @IBAction func goToStarTapped() {
57 | try? router.navigate(to: ConfigurationHolder.configuration.starScreen, with: "Test Context")
58 | }
59 |
60 | @IBAction func backProgrammaticallyTapped() {
61 | try? router.navigate(to: CitiesConfiguration.citiesList(cityId: nil))
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Cities/CityURLTranslator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // CityURLTranslator.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class CityURLTranslator: ExampleURLTranslator {
18 |
19 | func destination(from url: URL) -> AnyDestination? {
20 | guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false),
21 | let queryItems = urlComponents.queryItems,
22 | let cityItem = queryItems.first(where: { $0.name == "city" }),
23 | let cityValue = cityItem.value,
24 | let cityId = Int(cityValue) else {
25 | return nil
26 | }
27 |
28 | let cityDestination = CitiesConfiguration.cityDetail(cityId: cityId)
29 | return cityDestination.unwrapped()
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/ExampleNavigationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ExampleNavigationController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | // Its only purpose is to demo that you can reuse built-in actions with your custom classes
18 | class ExampleNavigationController: UINavigationController {
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | // It is better to use the appearance protocol for such modifications
23 | navigationBar.barTintColor = UIColor.lightGray
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/FiguresViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // FiguresViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class FiguresViewController: UIViewController, ExampleAnalyticsSupport {
18 |
19 | let screenType = ExampleScreenTypes.empty
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | title = "Figures"
24 | }
25 |
26 | @IBAction func goToCircleTapped() {
27 | try? router.navigate(to: ConfigurationHolder.configuration.circleScreen, with: nil)
28 | }
29 |
30 | @IBAction func goToSquareTapped() {
31 | try? router.navigate(to: ConfigurationHolder.configuration.squareScreen, with: nil)
32 | }
33 |
34 | @IBAction func goToSelfTapped() {
35 | try? router.navigate(to: ConfigurationHolder.configuration.figuresScreen, with: nil)
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Internal Search Demo/AnyContextCheckingViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyContextCheckingViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | // This view controller allows us to have the same ContextChecking UIViewController for testing
18 | // as its currently is swift it is impossible to write `some UIViewController: ContextChecking where Context == SOMETHING`
19 | class AnyContextCheckingViewController: UIViewController, ContextChecking {
20 |
21 | func isTarget(for context: Context) -> Bool {
22 | fatalError("Must be implemented in the child")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Internal Search Demo/HomeViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // HomeViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class HomeViewController: AnyContextCheckingViewController {
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | view.backgroundColor = .yellow
21 | title = "Home"
22 | view.accessibilityIdentifier = "myHomeViewController"
23 | }
24 |
25 | override func isTarget(for context: MainScreenContext) -> Bool {
26 | return context == .home
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Internal Search Demo/MainScreenContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // MainScreenContext.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | enum MainScreenContext: Equatable {
16 | case home
17 | case settings
18 | }
19 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Internal Search Demo/SettingsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SettingsViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class SettingsViewController: AnyContextCheckingViewController {
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | view.backgroundColor = .red
21 | title = "Settings"
22 | view.accessibilityIdentifier = "settingsViewController"
23 | }
24 |
25 | override func isTarget(for context: MainScreenContext) -> Bool {
26 | return context == .settings
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Login/LoginConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // LoginConfiguration.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 |
16 | enum LoginConfiguration {
17 |
18 | @MainActor
19 | static func login() -> Destination {
20 | let loginScreen = StepAssembly(finder: ClassFinder(),
21 | factory: NilFactory()) // Login view controller will be created when UINavigationController will be loaded from storyboard.
22 | .from(SingleStep(
23 | finder: NilFinder(),
24 | factory: StoryboardFactory(name: "Login")))
25 | .using( // `custom` and `overCurrentContext` are set for the test purposes only
26 | GeneralAction.presentModally(startingFrom: .custom(RouteComposerDefaults.shared.windowProvider.window?.topmostViewController),
27 | presentationStyle: .overCurrentContext))
28 | .from(GeneralStep.current())
29 | .assemble()
30 |
31 | return Destination(to: loginScreen)
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/No Library Dependency/Configuration With Library/ImageDetailsFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImageDetailsFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import ImageDetailsController
15 | import RouteComposer
16 | import UIKit
17 |
18 | class ImageDetailsFactory: Factory {
19 |
20 | typealias ViewController = ImageDetailsViewController
21 |
22 | typealias Context = String
23 |
24 | weak var delegate: ImageDetailsControllerDelegate?
25 |
26 | init(delegate: ImageDetailsControllerDelegate) {
27 | self.delegate = delegate
28 | }
29 |
30 | func build(with context: String) throws -> ImageDetailsViewController {
31 | guard let viewController = UIStoryboard(name: "Images", bundle: Bundle.main)
32 | .instantiateViewController(withIdentifier: "ImageDetailsViewController") as? ViewController else {
33 | throw RoutingError.compositionFailed(.init("Could not load ImagesViewController from the storyboard."))
34 | }
35 | viewController.delegate = delegate
36 | viewController.imageID = context
37 | viewController.imageFetcher = ImageDetailsFetcherImpl()
38 | return viewController
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/No Library Dependency/Configuration With Library/ImagesFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImagesFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import ImagesController
15 | import RouteComposer
16 | import UIKit
17 |
18 | class ImagesFactory: Factory {
19 |
20 | typealias ViewController = ImagesViewController
21 |
22 | typealias Context = Any?
23 |
24 | weak var delegate: ImagesControllerDelegate?
25 |
26 | init(delegate: ImagesControllerDelegate) {
27 | self.delegate = delegate
28 | }
29 |
30 | func build(with context: Any?) throws -> ImagesViewController {
31 | guard let viewController = UIStoryboard(name: "Images", bundle: Bundle.main)
32 | .instantiateViewController(withIdentifier: "ImagesViewController") as? ViewController else {
33 | throw RoutingError.compositionFailed(.init("Could not load ImagesViewController from the storyboard."))
34 | }
35 | viewController.delegate = delegate
36 | viewController.imageFetcher = ImageFetcherImpl()
37 | return viewController
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/No Library Dependency/Configuration With Library/ImagesWithLibraryHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImagesWithLibraryHandler.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import ContainerViewController
14 | import Foundation
15 | import ImageDetailsController
16 | import ImagesController
17 | import os.log
18 | import RouteComposer
19 | import UIKit
20 |
21 | class ImagesWithLibraryHandler: CustomViewControllerDelegate, ImagesControllerDelegate, ImageDetailsControllerDelegate {
22 |
23 | static let shared = ImagesWithLibraryHandler()
24 |
25 | func didSelect(imageID: String, in controller: ImagesViewController) {
26 | try? UIViewController.router.navigate(to: ImagesConfigurationWithLibrary.imageDetails(for: imageID), animated: true)
27 | }
28 |
29 | func dismissCustomContainer(controller: CustomContainerController) {
30 | controller.dismiss(animated: true)
31 | }
32 |
33 | func dismiss(imageDetails: ImageDetailsViewController) {
34 | try? UIViewController.router.navigate(to: ImagesConfigurationWithLibrary.images(), animated: true, completion: nil)
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/No Library Dependency/ImageDetailsFetcherImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImageDetailsFetcherImpl.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import ImageDetailsController
15 | import UIKit
16 |
17 | class ImageDetailsFetcherImpl: ImageDetailsFetcher {
18 |
19 | func details(for imageID: String, completion: @escaping (UIImage?) -> Void) {
20 | guard let image = UIImage(named: imageID) else {
21 | completion(nil)
22 | return
23 | }
24 |
25 | completion(image)
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/No Library Dependency/ImageFetcherImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ImageFetcherImpl.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import ImagesController
15 |
16 | class ImageFetcherImpl: ImagesFetcher {
17 |
18 | func loadImages(completion: @escaping ([String]) -> Void) {
19 | completion(["first", "second", "star"])
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/Product/ProductURLTranslator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ProductURLTranslator.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class ProductURLTranslator: ExampleURLTranslator {
18 |
19 | func destination(from url: URL) -> AnyDestination? {
20 | guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false),
21 | let queryItems = urlComponents.queryItems,
22 | let item = queryItems.first(where: { $0.name == "product" }),
23 | let productIdValue = item.value else {
24 | return nil
25 | }
26 |
27 | return Destination(to: ProductConfiguration.productScreen, with: ProductContext(productId: productIdValue, productURL: url)).unwrapped()
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/PromptViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // PromptViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class PromptViewController: UIViewController, ExampleAnalyticsSupport {
18 |
19 | let screenType = ExampleScreenTypes.welcome
20 |
21 | @IBAction func goToHomeTapped() {
22 | try? router.navigate(to: ConfigurationHolder.configuration.homeScreen, with: nil)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/SecondModalLevelViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SecondModalLevelViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class SecondModalLevelViewController: UIViewController, ExampleAnalyticsSupport {
18 |
19 | let screenType = ExampleScreenTypes.secondLevelModal
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | title = "Second modal level"
24 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped))
25 | }
26 |
27 | @IBAction func goToColorTapped() {
28 | try? router.navigate(to: ConfigurationHolder.configuration.colorScreen, with: "FF0000")
29 | }
30 |
31 | @IBAction func goToHomeTapped() {
32 | try? router.navigate(to: ConfigurationHolder.configuration.homeScreen, with: nil)
33 | }
34 |
35 | @IBAction func goToBerlinTapped() {
36 | try? router.navigate(to: CitiesConfiguration.cityDetail(cityId: 15), animated: false)
37 | }
38 |
39 | @objc func doneTapped() {
40 | dismiss(animated: true)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/SquareViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SquareViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import RouteComposer
14 | import UIKit
15 |
16 | class SquareViewController: UIViewController, ExampleAnalyticsSupport {
17 |
18 | let screenType = ExampleScreenTypes.square
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | title = "Square"
23 | }
24 |
25 | @IBAction func goToCircleTapped() {
26 | try? router.navigate(to: ConfigurationHolder.configuration.circleScreen, with: nil)
27 | }
28 |
29 | @IBAction func goToHomeTapped() {
30 | try? router.navigate(to: ConfigurationHolder.configuration.figuresScreen, with: nil)
31 | }
32 |
33 | @IBAction func goToSplitTapped() {
34 | try? router.navigate(to: CitiesConfiguration.citiesList())
35 | }
36 |
37 | @IBAction func goToLoginTapped() {
38 | try? router.navigate(to: LoginConfiguration.login())
39 | }
40 |
41 | @IBAction func goToStarTapped() {
42 | try? router.navigate(to: ConfigurationHolder.configuration.starScreen, with: "Test Context")
43 | }
44 |
45 | @IBAction func goToFakeContainerTapped() {
46 | try? router.navigate(to: WishListConfiguration.collections())
47 | }
48 |
49 | @IBAction func goEmptyAndProductTapped() {
50 | try? router.navigate(to: ConfigurationHolder.configuration.figuresAndProductScreen, with: ProductContext(productId: "03"))
51 | }
52 |
53 | @IBAction func switchValueChanged(sender: UISwitch) {
54 | if sender.isOn {
55 | ConfigurationHolder.configuration = AlternativeExampleConfiguration()
56 | } else {
57 | ConfigurationHolder.configuration = ExampleConfiguration()
58 | }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/StarViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // StarViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class StarViewController: UIViewController, ExampleAnalyticsSupport {
18 |
19 | let screenType = ExampleScreenTypes.star
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | title = "Star"
24 | tabBarItem.image = UIImage(named: "star")
25 | view.accessibilityIdentifier = "starViewController"
26 | }
27 |
28 | @IBAction func goToProductTapped() {
29 | try? router.navigate(to: ProductConfiguration.productScreen, with: ProductContext(productId: "02"))
30 | }
31 |
32 | @IBAction func goToCircleTapped() {
33 | try? router.navigate(to: ConfigurationHolder.configuration.circleScreen, with: nil)
34 | }
35 |
36 | @IBAction func dismissStarTapped() {
37 | var viewControllers = tabBarController?.viewControllers
38 | if let index = viewControllers?.firstIndex(of: self) {
39 | viewControllers?.remove(at: index)
40 | tabBarController?.setViewControllers(viewControllers, animated: true)
41 | }
42 | try? router.navigate(to: ConfigurationHolder.configuration.circleScreen, with: nil)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/SwiftUIContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SwiftUIContentView.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | #if canImport(SwiftUI)
16 | import SwiftUI
17 | #endif
18 |
19 | // NB: This view exists for the demo purposes only.
20 | @available(iOS 13.0.0, *)
21 | @MainActor
22 | struct SwiftUIContentView: View, ContextInstantiatable, ContextChecking, ContextAcceptingView {
23 |
24 | @State private var context: String = ""
25 |
26 | private var currentContext: String
27 |
28 | init(with context: String) {
29 | self.currentContext = context
30 | }
31 |
32 | var body: some View {
33 | VStack {
34 | Text("Hello SwiftUI. The context is \(context)")
35 | Button(action: {
36 | try? UIViewController.router.navigate(to: ConfigurationHolder.configuration.squareScreen, with: nil)
37 | }, label: {
38 | Text("Go to Square Tab")
39 | }).accessibility(identifier: "SwiftUI+\(context)")
40 | }.onAppear {
41 | context = currentContext
42 | }
43 | }
44 |
45 | func isTarget(for context: String) -> Bool {
46 | currentContext == context
47 | }
48 |
49 | mutating func setup(with context: String) throws {
50 | currentContext = context
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/WishList/WishListConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // WishListConfiguration.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | enum WishListConfiguration {
18 | @MainActor
19 | static let wishListScreen = StepAssembly(
20 | finder: ClassFinder(),
21 | factory: StoryboardFactory(name: "TabBar", identifier: "WishListViewController"))
22 | .adding(LoginInterceptor())
23 | .adding(WishListContextTask())
24 | .using(UINavigationController.push())
25 | .from(NavigationControllerStep())
26 | .using(GeneralAction.presentModally(presentationStyle: .formSheet))
27 | .from(GeneralStep.current())
28 | .assemble()
29 |
30 | @MainActor
31 | static func favorites() -> Destination {
32 | Destination(to: wishListScreen, with: WishListContext.favorites)
33 | }
34 |
35 | @MainActor
36 | static func collections() -> Destination {
37 | Destination(to: wishListScreen, with: WishListContext.collections)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/WishList/WishListContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // WishListContext.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | enum WishListContext: Int {
16 | case favorites = 0
17 | case collections
18 | }
19 |
20 | enum WishListDataModel {
21 |
22 | static let data = [
23 | WishListContext.favorites: ["Gucci", "Dolce & Gabbana", "Anna Valentine", "Lacoste"],
24 | .collections: ["Shoes", "Dresses", "Hats"]
25 | ]
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/Example/RouteComposer/View Controllers/WishList/WishListContextTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // WishListContextTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | import UIKit
16 |
17 | class WishListContextTask: ContextTask {
18 |
19 | func perform(on viewController: WishListViewController, with context: WishListContext) throws {
20 | viewController.context = context
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/Example/RouteComposer_ExampleUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example/RouteComposer_ExampleUITests/SwiftUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SwiftUITests.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import XCTest
15 |
16 | @MainActor
17 | class SwiftUITests: XCTestCase {
18 |
19 | override func setUp() {
20 | super.setUp()
21 | continueAfterFailure = false
22 | }
23 |
24 | override func tearDown() {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | super.tearDown()
27 | }
28 |
29 | func testSwiftUIViewFromProduct() {
30 | if #available(iOS 13, *) {
31 | let app = XCUIApplication()
32 | app.launchArguments.append("--uitesting")
33 | app.launch()
34 | XCTAssertTrue(app.otherElements["promptViewController"].exists)
35 | app.buttons["Continue"].tap()
36 | XCTAssertTrue(app.otherElements["homeViewController"].exists)
37 | app.buttons["Go to Product 00"].tap()
38 | XCTAssertTrue(app.otherElements["productViewController+00"].exists)
39 | app.buttons["Go to SwiftUI"].tap()
40 | XCTAssertTrue(app.buttons["SwiftUI+RouteComposer"].exists)
41 | app.buttons["Go to Square Tab"].tap()
42 | XCTAssertTrue(app.otherElements["squareViewController"].exists)
43 | app.terminate()
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/Example/Tests/Helpers/EmptyContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // EmptyContainer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | @testable import RouteComposer
15 | import UIKit
16 |
17 | struct EmptyContainer: SimpleContainerFactory {
18 |
19 | typealias ViewController = UINavigationController
20 |
21 | typealias Context = Any?
22 |
23 | init() {}
24 |
25 | func build(with context: Any?, integrating viewControllers: [UIViewController]) throws -> UINavigationController {
26 | let viewController = UINavigationController()
27 | viewController.viewControllers = viewControllers
28 | return viewController
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Example/Tests/Helpers/EmptyFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // EmptyFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | @testable import RouteComposer
15 | import UIKit
16 |
17 | struct EmptyFactory: Factory {
18 |
19 | init() {}
20 |
21 | func build(with context: Any?) throws -> UIViewController {
22 | UIViewController()
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Example/Tests/Helpers/TestStoryboard.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Tests/Helpers/TestSwiftUIView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // TestSwiftUIView.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import RouteComposer
15 | #if canImport(SwiftUI)
16 | import SwiftUI
17 | #endif
18 |
19 | @available(iOS 13.0.0, *)
20 | struct TestSwiftUIView: View, ContextInstantiatable, ContextChecking {
21 |
22 | let context: String
23 |
24 | init(with context: String) {
25 | self.context = context
26 | }
27 |
28 | var body: some View {
29 | Text("Hello SwiftUI!")
30 | }
31 |
32 | func isTarget(for context: String) -> Bool {
33 | self.context == context
34 | }
35 |
36 | }
37 |
38 | @available(iOS 13.0.0, *)
39 | struct TestSwiftUIAnyContextView: View, ContextInstantiatable, ContextChecking {
40 |
41 | let context: Context
42 |
43 | init(with context: Context) {
44 | self.context = context
45 | }
46 |
47 | var body: some View {
48 | Text("Hello SwiftUI!")
49 | }
50 |
51 | func isTarget(for context: Context) -> Bool {
52 | true
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Example/Tests/Helpers/TestWindowProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // TestWindowProvider.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | @testable import RouteComposer
15 | import UIKit
16 |
17 | class TestWindow: UIWindow {
18 | var isKey: Bool = false
19 |
20 | override func makeKeyAndVisible() {
21 | isKey = true
22 | }
23 |
24 | }
25 |
26 | struct TestWindowProvider: WindowProvider {
27 | let window: UIWindow?
28 |
29 | init(window: UIWindow) {
30 | self.window = window
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Example/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "slather", "2.6.1"
4 | gem "cocoapods"
5 | gem "jazzy"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Evgeny Kazaev.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.10
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "RouteComposer",
7 | platforms: [
8 | .iOS("16.0")
9 | ],
10 | products: [
11 | .library(
12 | name: "RouteComposer",
13 | targets: ["RouteComposer"]),
14 | .library(name: "RouteComposerStatic",
15 | type: .static,
16 | targets: ["RouteComposer"]),
17 | .library(name: "RouteComposerDynamic",
18 | type: .dynamic,
19 | targets: ["RouteComposer"])
20 | ],
21 | targets: [
22 | .target(
23 | name: "RouteComposer",
24 | dependencies: [],
25 | path: "RouteComposer/Classes"),
26 | .testTarget(
27 | name: "RouteComposerTests",
28 | dependencies: ["RouteComposer"],
29 | path: "Example/Tests")
30 | ],
31 | swiftLanguageVersions: [.version("6.0")])
32 |
--------------------------------------------------------------------------------
/RouteComposer.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'RouteComposer'
3 | s.version = '2.20.0'
4 | s.summary = 'Protocol oriented library that helps to handle view controllers composition, navigation and deep linking tasks.'
5 | s.swift_version = '6.1'
6 |
7 | s.description = <<-DESC
8 | RouteComposer is the protocol oriented, Cocoa UI abstractions based library that helps to handle view controllers composition, navigation
9 | and deep linking tasks in the iOS application. Can be used as the universal replacement for the Coordinator pattern.
10 | DESC
11 |
12 | s.homepage = 'https://github.com/ekazaev/route-composer'
13 | s.license = { :type => 'MIT', :file => 'LICENSE' }
14 | s.author = { 'Evgeny Kazaev' => 'eugene.kazaev@gmail.com' }
15 | s.source = { :git => 'https://github.com/ekazaev/route-composer.git', :tag => s.version }
16 |
17 | s.ios.deployment_target = '15.0'
18 | s.source_files = 'RouteComposer/Classes/**/*.*'
19 | s.frameworks = 'UIKit'
20 | end
21 |
--------------------------------------------------------------------------------
/RouteComposer.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/RouteComposer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/RouteComposer/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/RouteComposer/Assets/.gitkeep
--------------------------------------------------------------------------------
/RouteComposer/Classes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/RouteComposer/Classes/.gitkeep
--------------------------------------------------------------------------------
/RouteComposer/Classes/AbstractAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AbstractAction.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// Represents any action that has to be applied to the `UIViewController` after it has
16 | /// been built (eg: push to navigation stack, present modally, push to tab, etc)
17 | @MainActor
18 | public protocol AbstractAction {
19 |
20 | // MARK: Associated types
21 |
22 | /// Type of the `UIViewController` that `Action` can start from.
23 | associatedtype ViewController: UIViewController
24 |
25 | // MARK: Methods to implement
26 |
27 | /// Performs provided action to the view controller.
28 | ///
29 | /// - Parameters:
30 | /// - viewController: `UIViewController` instance that should appear on top of the stack
31 | /// after the `Action` is applied.
32 | /// - existingController: `UIViewController` instance to start from.
33 | /// - animated: animated
34 | /// - completion: called once the action is applied. Returns the view controller, which
35 | /// will appear on the top of the stack.
36 | ///
37 | /// NB: completion MUST be called in the implementation.
38 | func perform(with viewController: UIViewController,
39 | on existingController: ViewController,
40 | animated: Bool,
41 | completion: @escaping (_: RoutingResult) -> Void)
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Action.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // Action.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// Represents an action that has to be applied to the `UIViewController` after it has
16 | /// been built (eg: push to navigation stack, present modally, push to tab, etc)
17 | @MainActor
18 | public protocol Action: AbstractAction {}
19 |
20 | /// Represents an action to be used by a `ContainerFactory` to build it's children view controller stack
21 | @MainActor
22 | public protocol ContainerAction: AbstractAction where ViewController: ContainerViewController {
23 |
24 | // MARK: Methods to implement
25 |
26 | /// If current `UIViewController` has to be pushed/added/etc to the existing stack of the view controllers,
27 | /// this method should be called instead.
28 | ///
29 | /// - Parameters:
30 | /// - viewController: The `UIViewController` to be embedded.
31 | /// - childViewControllers: The stack of the `UIViewController`s in the current container.
32 | func perform(embedding viewController: UIViewController, in childViewControllers: inout [UIViewController]) throws
33 |
34 | }
35 |
36 | // MARK: Default implementation
37 |
38 | @MainActor
39 | public extension ContainerAction {
40 |
41 | func perform(embedding viewController: UIViewController, in childViewControllers: inout [UIViewController]) {
42 | childViewControllers.append(viewController)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Adapters/ConcreteContainerAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ConcreteContainerAdapter.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | /// Provides universal properties and methods of the `ContainerViewController` instance.
16 | public protocol ConcreteContainerAdapter: ContainerAdapter {
17 |
18 | // MARK: Associated types
19 |
20 | /// Type of `ContainerViewController`
21 | associatedtype Container: ContainerViewController
22 |
23 | // MARK: Methods to implement
24 |
25 | /// Constructor
26 | init(with viewController: Container)
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Adapters/CustomContainerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // CustomContainerViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Custom `ContainerViewController`s created outside of the library should extend this protocol, so `DefaultContainerAdapterLocator`
17 | /// could provide their `ContainerAdapter` to the `DefaultRouter` and other library's instances when needed.
18 | ///
19 | /// **NB:** If you want to substitute the `ContainerAdapter` for the container view controllers that are handled by library such as
20 | /// `UINavigationController` you may create the extensions for such container view controllers in your project.
21 | /// ```swift
22 | /// public extension UINavigationController: CustomContainerViewController {
23 | ///
24 | /// var adapter: ContainerAdapter {
25 | /// return CustomNavigationAdapter(with: self)
26 | /// }
27 | ///
28 | /// }
29 | /// ```
30 | public protocol CustomContainerViewController: ContainerViewController {
31 |
32 | // MARK: Properties to implement
33 |
34 | /// `ContainerAdapter` to be provided by `DefaultContainerAdapterLocator`
35 | var adapter: ContainerAdapter { get }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Assemblies/ChainAssembly.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ChainAssembly.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Builds a chain of steps.
17 | @MainActor
18 | public enum ChainAssembly {
19 |
20 | // MARK: Methods
21 |
22 | /// Transforms step into a chain of steps.
23 | /// ### Usage
24 | /// ```swift
25 | /// let intermediateStep = ChainAssembly.from(NavigationControllerStep())
26 | /// .using(GeneralAction.presentModally())
27 | /// .from(GeneralStep.current())
28 | /// .assemble()
29 | /// ```
30 | /// - Parameter step: The instance of `ActionConnectingAssembly`
31 | @MainActor
32 | public static func from(_ step: ActionToStepIntegrator) -> ActionConnectingAssembly {
33 | ActionConnectingAssembly(stepToFullFill: step, previousSteps: [])
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Assemblies/Helpers/BaseEntitiesCollector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // BaseEntitiesCollector.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | struct BaseEntitiesCollector: EntitiesProvider {
18 |
19 | let factory: AnyFactory?
20 |
21 | let finder: AnyFinder?
22 |
23 | init(finder: F, factory: FactoryBoxer.FactoryType, action: ActionBoxer.ActionType)
24 | where
25 | F.ViewController == FactoryBoxer.FactoryType.ViewController, F.Context == FactoryBoxer.FactoryType.Context {
26 | self.finder = FinderBox(finder)
27 |
28 | if let factoryBox = FactoryBoxer(factory, action: ActionBoxer(action)) {
29 | self.factory = factoryBox
30 | } else if let finderFactory = FinderFactory(finder: finder) {
31 | self.factory = FactoryBox(finderFactory, action: ActionBox(ViewControllerActions.NilAction()))
32 | } else {
33 | self.factory = nil
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Assemblies/Helpers/InterceptableStepAssembling.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InterceptableStepAssembling.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Assembly protocol allowing to build an interceptable step.
17 | @MainActor
18 | protocol InterceptableStepAssembling {
19 |
20 | // MARK: Associated types
21 |
22 | /// Supported `UIViewController` type
23 | associatedtype ViewController: UIViewController
24 |
25 | /// Supported `Context` type
26 | associatedtype Context
27 |
28 | // MARK: Methods to implement
29 |
30 | /// Adds `RoutingInterceptor` instance.
31 | /// This action does not contain type safety checks to avoid complications.
32 | ///
33 | /// - Parameter interceptor: The `RoutingInterceptor` instance to be executed by `Router` before the navigation process
34 | /// to this step.
35 | func adding(_ interceptor: RI) -> Self where RI.Context == Context
36 |
37 | /// Adds `ContextTask` instance
38 | ///
39 | /// - Parameter contextTask: The `ContextTask` instance to be applied by a `Router` immediately after it
40 | /// will find or create `UIViewController`.
41 | func adding(_ contextTask: CT) -> Self where CT.ViewController == ViewController, CT.Context == Context
42 |
43 | /// Adds `PostRoutingTask` instance.
44 | /// This action does not contain type safety checks to avoid complications.
45 | ///
46 | /// - Parameter postTask: The `PostRoutingTask` instance to be executed by a `Router` after the navigation process.
47 | func adding(_ postTask: PT) -> Self where PT.Context == Context
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Assemblies/Helpers/LastStepInChainAssembly.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // LastStepInChainAssembly.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Helper class to build a chain of steps. Can not be used directly.
17 | @MainActor
18 | public struct LastStepInChainAssembly {
19 |
20 | // MARK: Properties
21 |
22 | let previousSteps: [RoutingStep]
23 |
24 | // MARK: Methods
25 |
26 | init(previousSteps: [RoutingStep]) {
27 | self.previousSteps = previousSteps
28 | }
29 |
30 | /// Assembles all the provided settings.
31 | ///
32 | /// - Returns: The instance of `DestinationStep` with all the settings provided inside.
33 | public func assemble() -> DestinationStep {
34 | DestinationStep(chain(previousSteps))
35 | }
36 |
37 | private func chain(_ steps: [RoutingStep]) -> RoutingStep {
38 | guard let lastStep = steps.last else {
39 | preconditionFailure("No steps provided to chain.")
40 | }
41 |
42 | let firstStep = steps.dropLast().reversed().reduce(lastStep) { result, currentStep in
43 | guard var step = currentStep as? BaseStep else {
44 | assertionFailure("\(currentStep) can not be chained to non chainable step \(result)")
45 | return currentStep
46 | }
47 | step.from(result)
48 | return step
49 | }
50 |
51 | return firstStep
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Assemblies/Helpers/TaskCollector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // TaskCollector.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | struct TaskCollector: TaskProvider {
17 |
18 | private var interceptors: [AnyRoutingInterceptor] = []
19 |
20 | private var contextTasks: [AnyContextTask] = []
21 |
22 | private var postTasks: [AnyPostRoutingTask] = []
23 |
24 | mutating func add(_ interceptor: some RoutingInterceptor) {
25 | interceptors.append(RoutingInterceptorBox(interceptor))
26 | }
27 |
28 | mutating func add(_ contextTask: some ContextTask) {
29 | contextTasks.append(ContextTaskBox(contextTask))
30 | }
31 |
32 | mutating func add(_ postTask: some PostRoutingTask) {
33 | postTasks.append(PostRoutingTaskBox(postTask))
34 | }
35 |
36 | var interceptor: AnyRoutingInterceptor? {
37 | !interceptors.isEmpty ? interceptors.count == 1 ? interceptors.first : InterceptorMultiplexer(interceptors) : nil
38 | }
39 |
40 | var contextTask: AnyContextTask? {
41 | !contextTasks.isEmpty ? contextTasks.count == 1 ? contextTasks.first : ContextTaskMultiplexer(contextTasks) : nil
42 | }
43 |
44 | var postTask: AnyPostRoutingTask? {
45 | !postTasks.isEmpty ? postTasks.count == 1 ? postTasks.first : PostRoutingTaskMultiplexer(postTasks) : nil
46 | }
47 |
48 | init(interceptors: [AnyRoutingInterceptor] = [], contextTasks: [AnyContextTask] = [], postTasks: [AnyPostRoutingTask] = []) {
49 | self.interceptors = interceptors
50 | self.contextTasks = contextTasks
51 | self.postTasks = postTasks
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/ChildCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ChildCoordinator.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Helps to build a child view controller stack
17 | @MainActor
18 | public struct ChildCoordinator {
19 |
20 | // MARK: Properties
21 |
22 | var childFactories: [(factory: PostponedIntegrationFactory, context: AnyContext)]
23 |
24 | /// Returns `true` if the coordinator contains child factories to build
25 | public var isEmpty: Bool {
26 | childFactories.isEmpty
27 | }
28 |
29 | // MARK: Methods
30 |
31 | init(childFactories: [(factory: PostponedIntegrationFactory, context: AnyContext)]) {
32 | self.childFactories = childFactories
33 | }
34 |
35 | /// Builds child view controller stack with the context instance provided.
36 | ///
37 | /// - Parameters:
38 | /// - existingViewControllers: Current view controller stack of the container.
39 | /// - Returns: Built child view controller stack
40 | public func build(integrating existingViewControllers: [UIViewController] = []) throws -> [UIViewController] {
41 | var childrenViewControllers = existingViewControllers
42 | for factory in childFactories {
43 | try factory.factory.build(with: factory.context, in: &childrenViewControllers)
44 | }
45 | return childrenViewControllers
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/ContainerAdapterLocator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContainerAdapterLocator.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | /// Provides `ContainerAdapter` instance.
16 | @MainActor
17 | public protocol ContainerAdapterLocator {
18 |
19 | // MARK: Methods to implement
20 |
21 | /// Returns the `ContainerAdapter` suitable for the `ContainerViewController`
22 | ///
23 | /// - Parameter containerViewController: The `ContainerViewController` instance
24 | /// - Returns: Suitable `ContainerAdapter` instance
25 | /// - Throws: `RoutingError` if the suitable `ContainerAdapter` can not be provided
26 | func getAdapter(for containerViewController: ContainerViewController) throws -> ContainerAdapter
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/ContainerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContainerViewController.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// All the container view controllers should conform to this protocol.
17 | ///
18 | /// All the methods `ContainerViewController` supports are implemented in corresponding `ContainerAdapter`
19 | /// provided by `ContainerAdapterLocator`.
20 | @MainActor
21 | public protocol ContainerViewController: RoutingInterceptable {}
22 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/ContextTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextTransformer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | /// Transformer to be applied to transform one type of context to another.
16 | @MainActor
17 | public protocol ContextTransformer {
18 |
19 | // MARK: Associated types
20 |
21 | /// Type of source context
22 | associatedtype SourceContext
23 |
24 | /// Type of target context
25 | associatedtype TargetContext
26 |
27 | // MARK: Methods to implement
28 |
29 | /// Transforms one value into another.
30 | /// - Parameter context: Source content of type `SourceContext`
31 | /// - Returns: converted context of type `TargetContext`
32 | /// - Throws: The `Error` if `SourceContext` can not be converted to `TargetContext`.
33 | func transform(_ context: SourceContext) throws -> TargetContext
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extensions/Array+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // Array+Extension.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// - Extension of an `Array` of the `UIViewControllers` is to check if all of them can be dismissed.
17 | @MainActor
18 | public extension Array where Element: UIViewController {
19 |
20 | /// Returns `true` if all `UIViewController` instances can be dismissed.
21 | var canBeDismissed: Bool {
22 | nonDismissibleViewController == nil
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extensions/NavigationController+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // NavigationController+Extension.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// - The `UINavigationController` extension is to support the `ContainerViewController` protocol
17 | extension UINavigationController: ContainerViewController {
18 |
19 | public var canBeDismissed: Bool {
20 | viewControllers.canBeDismissed
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extensions/SplitViewController+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SplitViewController+Extension.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | // - The `UISplitViewController` extension is to support the `ContainerViewController` protocol
17 | extension UISplitViewController: ContainerViewController {
18 |
19 | public var canBeDismissed: Bool {
20 | viewControllers.canBeDismissed
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extensions/TabBarViewController+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // TabBarViewController+Extension.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// - The `UITabBarController` extension is to support the `ContainerViewController` protocol
17 | extension UITabBarController: ContainerViewController {
18 |
19 | public var canBeDismissed: Bool {
20 | viewControllers?.canBeDismissed ?? true
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extensions/UIWindow+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // UIWindow+Extension.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `UIWindow` helper functions.
17 | public extension UIWindow {
18 |
19 | /// The topmost `UIViewController` in the view controller stack.
20 | var topmostViewController: UIViewController? {
21 | var topmostViewController = rootViewController
22 |
23 | while let presentedViewController = topmostViewController?.presentedViewController, !presentedViewController.isBeingDismissed {
24 | topmostViewController = presentedViewController
25 | }
26 |
27 | return topmostViewController
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/ContextAccepting.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextAccepting.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// The protocol for a `UIViewController` to make it compatible with `ContextSettingTask`.
17 | public protocol ContextAccepting where Self: UIViewController {
18 |
19 | // MARK: Associated types
20 |
21 | /// Type of `Context` object that `UIViewController` can deal with
22 | associatedtype Context
23 |
24 | // MARK: Methods to implement
25 |
26 | /// If `UIViewController` does not support all the permutations that context instance may have -
27 | /// setup the check here.
28 | ///
29 | /// - Parameter context: `Context` instance.
30 | /// - Throws: throws `Error` if `Context` instance is not supported.
31 | @MainActor
32 | static func checkCompatibility(with context: Context) throws
33 |
34 | /// `ContextSettingTask` will call this method to provide the `Context` instance to the `UIViewController`
35 | /// that has just been build or found.
36 | ///
37 | /// - Parameter context: `Context` instance.
38 | /// - Throws: throws `Error` if `Context` instance is not supported. `Router` will stop building the rest of the stack in this case.
39 | func setup(with context: Context) throws
40 |
41 | }
42 |
43 | // MARK: Default implementation
44 |
45 | public extension ContextAccepting {
46 |
47 | /// Default implementation does nothing.
48 | @MainActor
49 | static func checkCompatibility(with context: Context) throws {}
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/ContextChecking.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextChecking.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `UIViewController` instance should conform to this protocol to be used with `ClassWithContextFinder`
17 | @MainActor
18 | public protocol ContextChecking {
19 |
20 | // MARK: Associated types
21 |
22 | /// The context type associated with a `ContextChecking` `UIViewController`
23 | associatedtype Context
24 |
25 | // MARK: Methods to implement
26 |
27 | /// If this view controller is suitable for the `Context` instance provided. Example: It is already showing the provided
28 | /// context data or is willing to do so, then it should return `true` or `false` if not.
29 | /// - Parameters:
30 | /// - context: The `Context` instance provided to the `Router`
31 | func isTarget(for context: Context) -> Bool
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/ContextSettingTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextSettingTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `ContextTask` that simplifies setting of the context to the `UIViewController` that implements `ContextAccepting` protocol.
17 | public struct ContextSettingTask: ContextTask {
18 |
19 | // MARK: Methods
20 |
21 | /// Constructor
22 | public init() {}
23 |
24 | public func prepare(with context: VC.Context) throws {
25 | try VC.checkCompatibility(with: context)
26 | }
27 |
28 | public func perform(on viewController: VC, with context: VC.Context) throws {
29 | try viewController.setup(with: context)
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/DismissalMethodProvidingContextTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // DismissalMethodProvidingContextTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `DismissalMethodProvidingContextTask` allows to provide the way to dismiss the `UIViewController` using a preset configuration.
17 | /// The `UIViewController` should conform to `Dismissible` protocol and call `Dismissible.dismissViewController(...)` method
18 | /// when it needs to be dismissed to trigger the dismissal process implemented in `DismissalMethodProvidingContextTask.init(...)`
19 | /// constructor.
20 | public struct DismissalMethodProvidingContextTask: ContextTask {
21 |
22 | // MARK: Properties
23 |
24 | let dismissalBlock: (_: VC, _: VC.DismissalTargetContext, _: Bool, _: ((_: RoutingResult) -> Void)?) -> Void
25 |
26 | // MARK: Methods
27 |
28 | /// Constructor
29 | ///
30 | /// - Parameter dismissalBlock: Block that will trigger the dismissal process when `Dismissible` `UIViewController` calls
31 | /// `Dismissible.dismissViewController(...)` method.
32 | public init(dismissalBlock: @escaping (_: VC, _: VC.DismissalTargetContext, _: Bool, _: ((_: RoutingResult) -> Void)?) -> Void) {
33 | self.dismissalBlock = dismissalBlock
34 | }
35 |
36 | public func perform(on viewController: VC, with context: C) throws {
37 | viewController.dismissalBlock = dismissalBlock
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/InlineContextTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InlineContextTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `InlineContextTask`
17 | ///
18 | /// **NB:** It may be used for the purpose of configuration testing, but then replaced with a strongly typed
19 | /// `ContextTask` instance.
20 | public struct InlineContextTask: ContextTask {
21 |
22 | // MARK: Properties
23 |
24 | private let completion: (_: VC, _: C) throws -> Void
25 |
26 | // MARK: Methods
27 |
28 | /// Constructor
29 | ///
30 | /// - Parameter completion: the block to be called when `InlineContextTask` will be applied to the `UIViewController`
31 | /// instance.
32 | public init(_ completion: @escaping (_: VC, _: C) throws -> Void) {
33 | self.completion = completion
34 | }
35 |
36 | public func perform(on viewController: VC, with context: C) throws {
37 | try completion(viewController, context)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/InlineContextTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InlineContextTransformer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | /// `InlineContextTransformer`
16 | ///
17 | /// **NB:** It may be used for the purpose of configuration testing, but then replaced with a strongly typed
18 | /// `ContextTransformer` instance.
19 | public final class InlineContextTransformer: ContextTransformer {
20 |
21 | // MARK: Properties
22 |
23 | private let transformationBlock: (SourceContext) throws -> TargetContext
24 |
25 | // MARK: Methods
26 |
27 | /// Constructor
28 | ///
29 | /// - Parameter transformationBlock: the block to be called when it requested to transform the context.
30 | public init(_ transformationBlock: @escaping (SourceContext) throws -> TargetContext) {
31 | self.transformationBlock = transformationBlock
32 | }
33 |
34 | public func transform(_ context: SourceContext) throws -> TargetContext {
35 | return try transformationBlock(context)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/InlineFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InlineFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// `InlineFactory`. Might be useful for the configuration testing.
16 | public struct InlineFactory: Factory {
17 |
18 | // MARK: Associated types
19 |
20 | /// Type of `UIViewController` that `Factory` can build
21 | public typealias ViewController = VC
22 |
23 | /// `Context` to be passed into `UIViewController`
24 | public typealias Context = C
25 |
26 | // MARK: Properties
27 |
28 | let inlineBock: (C) throws -> VC
29 |
30 | // MARK: Functions
31 |
32 | /// Constructor
33 | /// - Parameter inlineBock: the block to be called when `InlineFactory.build(...)` is requested.
34 | public init(viewController inlineBock: @autoclosure @escaping () throws -> VC) {
35 | self.inlineBock = { _ in
36 | try inlineBock()
37 | }
38 | }
39 |
40 | /// Constructor
41 | /// - Parameter inlineBock: the block to be called when `InlineFactory.build(...)` is requested.
42 | public init(_ inlineBock: @escaping (C) throws -> VC) {
43 | self.inlineBock = inlineBock
44 | }
45 |
46 | public func build(with context: C) throws -> VC {
47 | return try inlineBock(context)
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/InlinePostTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InlinePostTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `InlinePostTask` is the inline context task.
17 | ///
18 | /// **NB:** It may be used for the purpose of configuration testing, but then replaced with a strongly typed
19 | /// `PostRoutingTask` instance.
20 | public struct InlinePostTask: PostRoutingTask {
21 |
22 | // MARK: Properties
23 |
24 | private let completion: (_: VC, _: C, _: [UIViewController]) -> Void
25 |
26 | // MARK: Methods
27 |
28 | /// Constructor
29 | ///
30 | /// - Parameter completion: the block to be called when `InlinePostTask` will be called at the end of the navigation process
31 | /// process.
32 | public init(_ completion: @escaping (_: VC, _: C, _: [UIViewController]) -> Void) {
33 | self.completion = completion
34 | }
35 |
36 | public func perform(on viewController: VC, with context: C, routingStack: [UIViewController]) {
37 | completion(viewController, context, routingStack)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/Router+Destination.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // Router+Destination.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | public extension Router {
18 |
19 | // MARK: Navigation methods for the Destination instance
20 |
21 | /// Navigates the application to the view controller configured in `Destination` with the `Context` provided.
22 | ///
23 | /// - Parameters:
24 | /// - destination: `Destination` instance.
25 | /// - animated: if true - the navigation should be animated where it is possible.
26 | /// - completion: completion block.
27 | func navigate(to destination: Destination, animated: Bool = true, completion: ((_: RoutingResult) -> Void)? = nil) throws {
28 | try navigate(to: destination.step, with: destination.context, animated: animated, completion: completion)
29 | }
30 |
31 | /// Navigates the application to the view controller configured in `Destination` with the `Context` provided.
32 | /// Method does not throw errors, but propagates them to the completion block.
33 | ///
34 | /// - Parameters:
35 | /// - destination: `Destination` instance.
36 | /// - animated: if true - the navigation should be animated where it is possible.
37 | /// - completion: completion block.
38 | func commitNavigation(to destination: Destination, animated: Bool = true, completion: ((_: RoutingResult) -> Void)? = nil) {
39 | do {
40 | try navigate(to: destination, animated: animated, completion: completion)
41 | } catch {
42 | completion?(.failure(error))
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Extra/SwiftUI/ContextInstantiatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextInstantiatable.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | #if canImport(SwiftUI)
14 |
15 | import Foundation
16 | import SwiftUI
17 | import UIKit
18 |
19 | /// `View` instance should conform to this protocol to be used with `UIHostingControllerWithContextFactory`
20 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
21 | @MainActor
22 | public protocol ContextInstantiatable where Self: View {
23 |
24 | /// Type of `Context` object that `View` can be initialised with
25 | associatedtype Context
26 |
27 | /// Constructor
28 | init(with context: Context)
29 |
30 | }
31 |
32 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
33 | public extension ContextInstantiatable where Context == Void {
34 |
35 | /// Constructor
36 | init() {
37 | self.init(with: ())
38 | }
39 |
40 | }
41 |
42 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
43 | public extension ContextInstantiatable where Context == Any? {
44 |
45 | /// Constructor
46 | init() {
47 | self.init(with: nil)
48 | }
49 |
50 | }
51 |
52 | #endif
53 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Factories/ClassFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ClassFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// The `Factory` that creates a `UIViewController` instance using its type.
16 | public struct ClassFactory: Factory {
17 |
18 | // MARK: Associated types
19 |
20 | public typealias ViewController = VC
21 |
22 | public typealias Context = C
23 |
24 | // MARK: Properties
25 |
26 | /// A Xib file name
27 | public let nibName: String?
28 |
29 | /// A `Bundle` instance
30 | public let bundle: Bundle?
31 |
32 | /// The additional configuration block
33 | public let configuration: ((_: VC) -> Void)?
34 |
35 | // MARK: Methods
36 |
37 | /// Constructor
38 | ///
39 | /// - Parameters:
40 | /// - nibNameOrNil: A Xib file name
41 | /// - nibBundleOrNil: A `Bundle` instance if needed
42 | /// - configuration: A block of code that will be used for the extended configuration of the created `UIViewController`. Can be used for
43 | /// a quick configuration instead of `ContextTask`.
44 | public init(nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil, configuration: ((_: VC) -> Void)? = nil) {
45 | self.nibName = nibNameOrNil
46 | self.bundle = nibBundleOrNil
47 | self.configuration = configuration
48 | }
49 |
50 | public func build(with context: C) throws -> VC {
51 | let viewController = VC(nibName: nibName, bundle: bundle)
52 | if let configuration {
53 | configuration(viewController)
54 | }
55 | return viewController
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Factories/FinderFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // FinderFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// The `StepAssembly` transforms a `Finder` result as a `Factory` result. It is useful
17 | /// when a `UIViewController` instance was built inside of the parent `ContainerFactory`.
18 | public struct FinderFactory: Factory {
19 |
20 | // MARK: Associated types
21 |
22 | public typealias ViewController = F.ViewController
23 |
24 | public typealias Context = F.Context
25 |
26 | // MARK: Properties
27 |
28 | /// The additional configuration block
29 | public let configuration: ((_: F.ViewController) -> Void)?
30 |
31 | private let finder: F
32 |
33 | // MARK: Methods
34 |
35 | /// Constructor
36 | ///
37 | /// - Parameters:
38 | /// - finder: The `Finder` instance to be used by the `Factory`
39 | public init?(finder: F, configuration: ((_: F.ViewController) -> Void)? = nil) {
40 | guard !(finder is NilEntity) else {
41 | return nil
42 | }
43 | self.finder = finder
44 | self.configuration = configuration
45 | }
46 |
47 | public func build(with context: F.Context) throws -> F.ViewController {
48 | guard let viewController = try finder.findViewController(with: context) else {
49 | throw RoutingError.compositionFailed(.init("\(String(describing: finder)) hasn't found its view controller in the stack."))
50 | }
51 | if let configuration {
52 | configuration(viewController)
53 | }
54 | return viewController
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Factories/NilFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // NilFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// The dummy struct used to represent the `Factory` that does not build anything.
17 | /// Its only purpose is to provide the type safety checks for the `StepAssembly`.
18 | ///
19 | /// For example, the `UIViewController` of the step was already loaded and integrated into a stack by a
20 | /// storyboard in a previous step.
21 | public struct NilFactory: Factory, NilEntity {
22 |
23 | // MARK: Associated types
24 |
25 | public typealias ViewController = VC
26 |
27 | public typealias Context = C
28 |
29 | // MARK: Methods
30 |
31 | /// Constructor
32 | public init() {}
33 |
34 | public func prepare(with context: C) throws {
35 | throw RoutingError.compositionFailed(.init("This factory can not build any UIViewController."))
36 | }
37 |
38 | public func build(with context: C) throws -> VC {
39 | throw RoutingError.compositionFailed(.init("This factory can not build any UIViewController."))
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Factories/SimpleContainerFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SimpleContainerFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// A helper protocol to the `ContainerFactory` protocol. If a container does not need to deal with the children view
17 | /// controller creation, `SimpleContainerFactory` will handle integration of the children view controllers.
18 | public protocol SimpleContainerFactory: ContainerFactory {
19 |
20 | // MARK: Associated types
21 |
22 | /// Type of `UIViewController` that `SimpleContainerFactory` can build
23 | associatedtype ViewController
24 |
25 | /// `Context` to be passed into `UIViewController`
26 | associatedtype Context
27 |
28 | // MARK: Methods to implement
29 |
30 | /// Builds a `UIViewController` that will be integrated into the stack
31 | ///
32 | /// Parameters:
33 | /// - context: A `Context` instance provided to the `Router`.
34 | /// - viewControllers: `UIViewController` instances to be integrated into the container as children view controllers
35 | /// - Returns: The built `UIViewController` container instance.
36 | /// - Throws: The `RoutingError` if the build does not succeed.
37 | func build(with context: Context, integrating viewControllers: [UIViewController]) throws -> ViewController
38 |
39 | }
40 |
41 | @MainActor
42 | public extension SimpleContainerFactory {
43 |
44 | /// Default implementation of the `ContainerFactory`'s `build` method
45 | func build(with context: Context, integrating coordinator: ChildCoordinator) throws -> ViewController {
46 | let viewControllers = try coordinator.build()
47 | return try build(with: context, integrating: viewControllers)
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Factories/SwiftUI/UIHostingControllerFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // UIHostingControllerFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | #if canImport(SwiftUI)
14 |
15 | import Foundation
16 | import SwiftUI
17 | import UIKit
18 |
19 | /// Builds `UIHostingController` with `ContentView` as a `UIHostingController.rootView` using the provided block.
20 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
21 | public struct UIHostingControllerFactory: Factory {
22 |
23 | // MARK: Associated types
24 |
25 | public typealias ViewController = UIHostingController
26 |
27 | public typealias Context = Context
28 |
29 | // MARK: Properties
30 |
31 | private let buildBlock: (Context) -> ContentView
32 |
33 | // MARK: Methods
34 |
35 | /// Constructor
36 | /// - Parameter buildBlock: Block that builds the `View` with the using the `Context` instance provided.
37 | public init(_ buildBlock: @escaping (Context) -> ContentView) {
38 | self.buildBlock = buildBlock
39 | }
40 |
41 | public func build(with context: Context) throws -> UIHostingController {
42 | let viewController = UIHostingController(rootView: buildBlock(context))
43 | return viewController
44 | }
45 |
46 | }
47 |
48 | #endif
49 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Factories/SwiftUI/UIHostingControllerWithContextFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // UIHostingControllerWithContextFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | #if canImport(SwiftUI)
14 |
15 | import Foundation
16 | import SwiftUI
17 | import UIKit
18 |
19 | /// Builds `UIHostingController` with `ContentView` as a `UIHostingController.rootView` using the constructor
20 | /// provided with `ContextInstantiatable` implementation.
21 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
22 | public struct UIHostingControllerWithContextFactory: Factory {
23 |
24 | // MARK: Associated types
25 |
26 | public typealias ViewController = UIHostingController
27 |
28 | public typealias Context = ContentView.Context
29 |
30 | // MARK: Methods
31 |
32 | /// Constructor
33 | public init() {}
34 |
35 | public func build(with context: Context) throws -> UIHostingController {
36 | let viewController = UIHostingController(rootView: ContentView(with: context))
37 | return viewController
38 | }
39 |
40 | }
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Finders/InstanceFinder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InstanceFinder.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// The `Finder` that provides the `Router` a known instance of the `UIViewController`
17 | public struct InstanceFinder: Finder {
18 |
19 | // MARK: Associated types
20 |
21 | public typealias ViewController = VC
22 |
23 | public typealias Context = C
24 |
25 | // MARK: Properties
26 |
27 | /// The `UIViewController` instance that `Finder` will provide to the `Router`
28 | public private(set) weak var instance: VC?
29 |
30 | // MARK: Methods
31 |
32 | /// Constructor
33 | ///
34 | /// - Parameters:
35 | /// - instance: The `UIViewController` instance that `Finder` should provide to the `Router`
36 | public init(instance: VC) {
37 | self.instance = instance
38 | }
39 |
40 | public func findViewController(with context: C) throws -> VC? {
41 | instance
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Finders/NilFinder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // NilFinder.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Dummy struct used to represent that nothing should be found in a view controller stack
17 | /// and a `UIViewController` should always be created from scratch.
18 | /// Its only purpose is to provide type safety checks for `StepAssembly`.
19 | ///
20 | /// For example, `UIViewController` of this step was already loaded and integrated into a stack by a storyboard.
21 | public struct NilFinder: Finder, NilEntity {
22 |
23 | // MARK: Associated types
24 |
25 | public typealias ViewController = VC
26 |
27 | public typealias Context = C
28 |
29 | // MARK: Methods
30 |
31 | /// Constructor
32 | public init() {}
33 |
34 | /// `Finder` method empty implementation.
35 | ///
36 | /// - Parameter context: A context instance provided.
37 | /// - Returns: always `nil`.
38 | public func findViewController(with context: C) throws -> VC? {
39 | nil
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Finders/Stack Iterator/CustomWindowProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // CustomWindowProvider.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Returns custom `UIWindow`
17 | public struct CustomWindowProvider: WindowProvider {
18 |
19 | // MARK: Properties
20 |
21 | /// Returns key `UIWindow`
22 | public weak var window: UIWindow?
23 |
24 | // MARK: Methods
25 |
26 | /// Constructor
27 | public init(window: UIWindow) {
28 | self.window = window
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Finders/Stack Iterator/KeyWindowProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // KeyWindowProvider.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Returns key `UIWindow`
17 | @MainActor
18 | public struct KeyWindowProvider: WindowProvider {
19 |
20 | // MARK: Properties
21 |
22 | /// `UIWindow` instance
23 | public var window: UIWindow? {
24 | let keyWindow: UIWindow? = if #available(iOS 13, *) {
25 | UIApplication.shared.windows.first { $0.isKeyWindow }
26 | } else {
27 | UIApplication.shared.keyWindow
28 | }
29 | guard let window = keyWindow else {
30 | assertionFailure("Application does not have a key window.")
31 | return nil
32 | }
33 | return window
34 | }
35 |
36 | // MARK: Methods
37 |
38 | /// Constructor
39 | public nonisolated init() {}
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Finders/Stack Iterator/StackIterator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // StackIterator.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `StackIterator` protocol
17 | @MainActor
18 | public protocol StackIterator {
19 |
20 | // MARK: Methods to implement
21 |
22 | /// Returns `UIViewController` instance if found
23 | ///
24 | /// - Parameter predicate: A block that contains `UIViewController` matching condition
25 | func firstViewController(where predicate: (UIViewController) -> Bool) throws -> UIViewController?
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Finders/Stack Iterator/WindowProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // WindowProvider.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Provides `UIWindow`
17 | @MainActor
18 | public protocol WindowProvider {
19 |
20 | // MARK: Properties to implement
21 |
22 | /// `UIWindow` instance
23 | var window: UIWindow? { get }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/InterceptableRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InterceptableRouter.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// The router implementing this protocol should support global tasks.
17 | @MainActor
18 | public protocol InterceptableRouter: Router {
19 |
20 | /// Adds `RoutingInterceptor` instance
21 | ///
22 | /// - Parameter interceptor: The `RoutingInterceptor` instance to be executed by `Router` before routing to this step.
23 | mutating func add(_ interceptor: RI) where RI.Context == Any?
24 |
25 | /// Adds ContextTask instance
26 | ///
27 | /// - Parameter contextTask: The `ContextTask` instance to be applied by a `Router` immediately after it will find
28 | /// or create `UIViewController`.
29 | mutating func add(_ contextTask: CT) where CT.ViewController == UIViewController, CT.Context == Any?
30 |
31 | /// Adds PostRoutingTask instance
32 | ///
33 | /// - Parameter postTask: The `PostRoutingTask` instance to be executed by a `Router` after routing to this step.
34 | mutating func add(_ postTask: PT) where PT.Context == Any?
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Logger/DefaultLogger+LogLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // DefaultLogger+LogLevel.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | public extension DefaultLogger {
16 |
17 | /// Log level settings
18 | ///
19 | /// - verbose: Log all the messages from a `Router`
20 | /// - warnings: Log only warnings and errors
21 | /// - errors: Log only errors
22 | enum LogLevel: CaseIterable {
23 |
24 | /// Log all the messages from `Router`
25 | case verbose
26 |
27 | /// Log only warnings and errors
28 | case warnings
29 |
30 | /// Log only errors
31 | case errors
32 |
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Logger/DefaultLogger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // DefaultLogger.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import os.log
15 |
16 | /// Default Logger implementation
17 | public struct DefaultLogger: Logger {
18 |
19 | // MARK: Properties
20 |
21 | /// Log level
22 | public let logLevel: LogLevel
23 |
24 | private let osLog: OSLog
25 |
26 | // MARK: Methods
27 |
28 | /// Constructor.
29 | ///
30 | /// - Parameters:
31 | /// - logLevel: DefaultLoggerLevel. Defaulted to warnings.
32 | /// - osLog: OSLog instance of the app.
33 | public init(_ logLevel: LogLevel = .warnings,
34 | osLog: OSLog = OSLog.default) {
35 | self.logLevel = logLevel
36 | self.osLog = osLog
37 | }
38 |
39 | public func log(_ message: LogMessage) {
40 | switch message {
41 | case let .warning(message):
42 | if logLevel == .verbose || logLevel == .warnings {
43 | os_log("%@", log: osLog, type: .error, message)
44 | }
45 | case let .info(message):
46 | if logLevel == .verbose {
47 | os_log("%@", log: osLog, type: .info, message)
48 | }
49 | case let .error(message):
50 | os_log("%@", log: osLog, type: .fault, message)
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Logger/LogMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // LogMessage.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | /// `Logger` message representation
16 | ///
17 | /// - `info`: info message
18 | /// - `warning`: warning message
19 | /// - `error`: error message
20 | public enum LogMessage {
21 |
22 | /// info message
23 | case info(String)
24 |
25 | /// warning message
26 | case warning(String)
27 |
28 | /// error message
29 | case error(String)
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Logger/Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // Logger.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// Routing logger protocol
17 | public protocol Logger {
18 |
19 | // MARK: Methods to implement
20 |
21 | /// Logs a message
22 | ///
23 | /// - Parameters:
24 | /// - message: The `LogMessage` instance
25 | func log(_ message: LogMessage)
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Helpers/StackPresentationHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // StackPresentationHandler.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// Helper instance used to update the stack of `UIViewController`s
16 | @MainActor
17 | public protocol StackPresentationHandler {
18 |
19 | // MARK: Methods to implement
20 |
21 | /// Dismisses all the `UIViewController`s presented on top of the provided `UIViewController`.
22 | /// - Parameters:
23 | /// - viewController: `UIViewController` to dismiss presented `UIViewController`s from.
24 | /// - animated: Update stack with animation where possible.
25 | /// - completion: Completion block
26 | func dismissPresented(from viewController: UIViewController,
27 | animated: Bool,
28 | completion: @escaping ((_: RoutingResult) -> Void))
29 |
30 | /// Makes the provided `UIViewController` visible in all the enclosing containers.
31 | /// - Parameters:
32 | /// - viewController: `UIViewController` to make visible.
33 | /// - animated: Update stack with animation where possible.
34 | /// - completion: Completion block
35 | func makeVisibleInParentContainers(_ viewController: UIViewController,
36 | animated: Bool,
37 | completion: @escaping (RoutingResult) -> Void)
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/Array+PrivateExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // Array+PrivateExtension.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | extension Array where Element: UIViewController {
18 |
19 | var nonDismissibleViewController: UIViewController? {
20 | compactMap {
21 | $0 as? RoutingInterceptable & UIViewController
22 | }.first {
23 | !$0.canBeDismissed
24 | }
25 | }
26 |
27 | func uniqueElements() -> [Element] {
28 | reduce(into: [Element]()) {
29 | if !$0.contains($1) {
30 | $0.append($1)
31 | }
32 | }
33 | }
34 |
35 | func isEqual(to array: [UIViewController]) -> Bool {
36 | guard count == array.count else {
37 | return false
38 | }
39 | return enumerated().first(where: { index, vc in
40 | array[index] !== vc
41 | }) == nil
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/ChainableStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ChainableStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | // Chainable step.
17 | // Identifies that the step can be a part of the chain,
18 | // e.g. when it comes to the presentation of multiple view controllers to reach destination.
19 | @MainActor
20 | protocol ChainableStep {
21 |
22 | // `RoutingStep` to be made by a `Router` before getting to this step.
23 | func getPreviousStep(with context: AnyContext) -> RoutingStep?
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/ConvertingStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ConvertingStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | struct ConvertingStep: RoutingStep,
16 | ChainableStep,
17 | PerformableStep {
18 |
19 | private let contextTransformer: CT
20 | private var previousStep: RoutingStep?
21 |
22 | init(contextTransformer: CT, previousStep: RoutingStep?) {
23 | self.contextTransformer = contextTransformer
24 | self.previousStep = previousStep
25 | }
26 |
27 | func getPreviousStep(with context: AnyContext) -> RoutingStep? {
28 | return previousStep
29 | }
30 |
31 | func perform(with context: AnyContext) throws -> PerformableStepResult {
32 | let typedContext: CT.SourceContext = try context.value()
33 | let newContext = try contextTransformer.transform(typedContext)
34 | return .updateContext(AnyContextBox(newContext))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/InPlaceTransformingAnyContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InPlaceTransformingAnyContext.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | struct InPlaceTransformingAnyContext: AnyContext {
16 | let context: AnyContext
17 | let transformer: AnyContextTransformer
18 |
19 | init(context: AnyContext, transformer: AnyContextTransformer) {
20 | self.context = context
21 | self.transformer = transformer
22 | }
23 |
24 | func value() throws -> Context {
25 | return try transformer.transform(context)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/InterceptableStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InterceptableStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | protocol InterceptableStep where Self: PerformableStep {
17 |
18 | var interceptor: AnyRoutingInterceptor? { get }
19 |
20 | var postTask: AnyPostRoutingTask? { get }
21 |
22 | var contextTask: AnyContextTask? { get }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/NilContextTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // NilContextTransformer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | struct NilContextTransformer: ContextTransformer {
16 | func transform(_ context: Context) throws -> Context {
17 | return context
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/NilEntity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // NilEntity.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | /// The Protocol that explains to the library that entity should be ignored.
16 | public protocol NilEntity {}
17 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/PerformableStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // PerformableStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | protocol PerformableStep {
17 |
18 | /// - Parameter context: The `Context` instance that `Router` has started with.
19 | /// - Returns: The `StepResult` enum value, which may contain a view controller in case of `.success` scenario.
20 | func perform(with context: AnyContext) throws -> PerformableStepResult
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/PerformableStepResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // PerformableStepResult.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | enum PerformableStepResult {
17 |
18 | case updateContext(AnyContext)
19 |
20 | case success(UIViewController)
21 |
22 | case build(AnyFactory)
23 |
24 | case none
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/PreparableEntity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // PreparableEntity.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | protocol PreparableEntity {
16 |
17 | var isPrepared: Bool { get }
18 |
19 | }
20 |
21 | extension PreparableEntity {
22 |
23 | func assertIfNotPrepared() {
24 | if !isPrepared {
25 | assertionFailure("Internal inconsistency: prepare(with:) method has never been " +
26 | "called for \(String(describing: self)).")
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/RoutingStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // RoutingStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// Represents a single step for the `Router` to make.
16 | @MainActor
17 | protocol RoutingStep {}
18 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Internal/SwitcherStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SwitcherStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol StepCaseResolver {
18 |
19 | func resolve(with context: AnyContext) -> RoutingStep?
20 |
21 | }
22 |
23 | @MainActor
24 | final class SwitcherStep: RoutingStep, ChainableStep {
25 |
26 | final var resolvers: [StepCaseResolver]
27 |
28 | final func getPreviousStep(with context: AnyContext) -> RoutingStep? {
29 | resolvers.reduce(nil as RoutingStep?) { result, resolver in
30 | guard result == nil else {
31 | return result
32 | }
33 | return resolver.resolve(with: context)
34 | }
35 | }
36 |
37 | init(resolvers: [StepCaseResolver]) {
38 | self.resolvers = resolvers
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Multiplexers/ContextTaskMultiplexer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextTaskMultiplexer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | struct ContextTaskMultiplexer: AnyContextTask, @preconcurrency CustomStringConvertible {
17 |
18 | private var tasks: [AnyContextTask]
19 |
20 | init(_ tasks: [AnyContextTask]) {
21 | self.tasks = tasks
22 | }
23 |
24 | mutating func prepare(with context: AnyContext) throws {
25 | tasks = try tasks.map {
26 | var contextTask = $0
27 | try contextTask.prepare(with: context)
28 | return contextTask
29 | }
30 | }
31 |
32 | func perform(on viewController: UIViewController, with context: AnyContext) throws {
33 | try tasks.forEach { try $0.perform(on: viewController, with: context) }
34 | }
35 |
36 | var description: String {
37 | String(describing: tasks)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Multiplexers/InterceptorMultiplexer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // InterceptorMultiplexer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | struct InterceptorMultiplexer: AnyRoutingInterceptor, @preconcurrency CustomStringConvertible {
16 |
17 | private var interceptors: [AnyRoutingInterceptor]
18 |
19 | init(_ interceptors: [AnyRoutingInterceptor]) {
20 | self.interceptors = interceptors
21 | }
22 |
23 | mutating func prepare(with context: AnyContext) throws {
24 | interceptors = try interceptors.map {
25 | var interceptor = $0
26 | try interceptor.prepare(with: context)
27 | return interceptor
28 | }
29 | }
30 |
31 | func perform(with context: AnyContext, completion: @escaping (RoutingResult) -> Void) {
32 | guard !self.interceptors.isEmpty else {
33 | completion(.success)
34 | return
35 | }
36 |
37 | var interceptors = interceptors
38 |
39 | func runInterceptor(interceptor: AnyRoutingInterceptor) {
40 | interceptor.perform(with: context) { result in
41 | if case .failure = result {
42 | completion(result)
43 | } else if interceptors.isEmpty {
44 | completion(result)
45 | } else {
46 | runInterceptor(interceptor: interceptors.removeFirst())
47 | }
48 | }
49 | }
50 |
51 | runInterceptor(interceptor: interceptors.removeFirst())
52 | }
53 |
54 | var description: String {
55 | String(describing: interceptors)
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Multiplexers/PostRoutingTaskMultiplexer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // PostRoutingTaskMultiplexer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | struct PostRoutingTaskMultiplexer: AnyPostRoutingTask, @preconcurrency CustomStringConvertible {
17 |
18 | private let tasks: [AnyPostRoutingTask]
19 |
20 | init(_ tasks: [AnyPostRoutingTask]) {
21 | self.tasks = tasks
22 | }
23 |
24 | func perform(on viewController: UIViewController, with context: AnyContext, routingStack: [UIViewController]) throws {
25 | try tasks.forEach { try $0.perform(on: viewController, with: context, routingStack: routingStack) }
26 | }
27 |
28 | var description: String {
29 | String(describing: tasks)
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyAction.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol PostponedActionIntegrationHandler: AnyObject {
18 |
19 | var containerViewController: ContainerViewController? { get }
20 |
21 | var postponedViewControllers: [UIViewController] { get }
22 |
23 | func update(containerViewController: ContainerViewController, animated: Bool, completion: @escaping (_: RoutingResult) -> Void)
24 |
25 | func update(postponedViewControllers: [UIViewController])
26 |
27 | func purge(animated: Bool, completion: @escaping (_: RoutingResult) -> Void)
28 |
29 | }
30 |
31 | @MainActor
32 | protocol AnyAction {
33 |
34 | func perform(with viewController: UIViewController,
35 | on existingController: UIViewController,
36 | with postponedIntegrationHandler: PostponedActionIntegrationHandler,
37 | nextAction: AnyAction?,
38 | animated: Bool,
39 | completion: @escaping (_: RoutingResult) -> Void)
40 |
41 | func perform(embedding viewController: UIViewController,
42 | in childViewControllers: inout [UIViewController]) throws
43 |
44 | func isEmbeddable(to container: ContainerViewController.Type) -> Bool
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyContext.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | protocol AnyContext {
17 | func value() throws -> Context
18 | }
19 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyContextTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyContextTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol AnyContextTask {
18 |
19 | mutating func prepare(with context: AnyContext) throws
20 |
21 | func perform(on viewController: UIViewController, with context: AnyContext) throws
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyContextTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyContextTransformer.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | protocol AnyContextTransformer {
17 |
18 | func transform(_ context: AnyContext) throws -> Context
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyFactory.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol AnyFactory {
18 |
19 | var action: AnyAction { get }
20 |
21 | mutating func prepare(with context: AnyContext) throws
22 |
23 | func build(with context: AnyContext) throws -> UIViewController
24 |
25 | mutating func scrapeChildren(from factories: [(factory: AnyFactory, context: AnyContext)]) throws -> [(factory: AnyFactory, context: AnyContext)]
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyFinder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyFinder.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol AnyFinder {
18 |
19 | func findViewController(with context: AnyContext) throws -> UIViewController?
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyPostRoutingTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyPostRoutingTask.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol AnyPostRoutingTask {
18 |
19 | func perform(on viewController: UIViewController,
20 | with context: AnyContext,
21 | routingStack: [UIViewController]) throws
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/AnyRoutingInterceptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyRoutingInterceptor.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | protocol AnyRoutingInterceptor {
18 |
19 | mutating func prepare(with context: AnyContext) throws
20 |
21 | func perform(with context: AnyContext, completion: @escaping (_: RoutingResult) -> Void)
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/AnyActionBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyActionBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | protocol AnyActionBox: AnyAction {
16 |
17 | associatedtype ActionType: AbstractAction
18 |
19 | init(_ action: ActionType)
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/AnyContextBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyContextBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | struct AnyContextBox: AnyContext {
16 | let context: C
17 |
18 | init(_ context: C) {
19 | self.context = context
20 | }
21 |
22 | func value() throws -> Context {
23 | guard let typedContext = context as? Context else {
24 | throw RoutingError.typeMismatch(type: type(of: context),
25 | expectedType: Context.self,
26 | .init("\(String(describing: context.self)) can not be converted to \(String(describing: Context.self))."))
27 | }
28 | return typedContext
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/AnyFactoryBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // AnyFactoryBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | @MainActor
16 | protocol AnyFactoryBox: AnyFactory {
17 |
18 | associatedtype FactoryType: AbstractFactory
19 |
20 | var factory: FactoryType { get set }
21 |
22 | init?(_ factory: FactoryType, action: AnyAction)
23 |
24 | }
25 |
26 | @MainActor
27 | protocol PreparableAnyFactory: AnyFactory, PreparableEntity {
28 |
29 | var isPrepared: Bool { get set }
30 |
31 | }
32 |
33 | @MainActor
34 | extension AnyFactoryBox {
35 |
36 | mutating func scrapeChildren(from factories: [(factory: AnyFactory, context: AnyContext)]) throws -> [(factory: AnyFactory, context: AnyContext)] {
37 | factories
38 | }
39 |
40 | }
41 |
42 | @MainActor
43 | extension AnyFactoryBox where Self: PreparableAnyFactory {
44 |
45 | mutating func prepare(with context: AnyContext) throws {
46 | let typedContext: FactoryType.Context = try context.value()
47 | try factory.prepare(with: typedContext)
48 | isPrepared = true
49 | }
50 |
51 | }
52 |
53 | @MainActor
54 | extension AnyFactory where Self: CustomStringConvertible & AnyFactoryBox {
55 |
56 | var description: String {
57 | String(describing: factory)
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/ContextTaskBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextTaskBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | struct ContextTaskBox: AnyContextTask, PreparableEntity, @preconcurrency CustomStringConvertible {
17 |
18 | var contextTask: CT
19 |
20 | var isPrepared = false
21 |
22 | init(_ contextTask: CT) {
23 | self.contextTask = contextTask
24 | }
25 |
26 | mutating func prepare(with context: AnyContext) throws {
27 | let typedContext: CT.Context = try context.value()
28 | try contextTask.prepare(with: typedContext)
29 | isPrepared = true
30 | }
31 |
32 | func perform(on viewController: UIViewController, with context: AnyContext) throws {
33 | guard let typedViewController = viewController as? CT.ViewController else {
34 | throw RoutingError.typeMismatch(type: type(of: context),
35 | expectedType: CT.Context.self,
36 | .init("\(String(describing: contextTask.self)) does not accept \(String(describing: context.self)) as a context."))
37 | }
38 | let typedContext: CT.Context = try context.value()
39 |
40 | assertIfNotPrepared()
41 | try contextTask.perform(on: typedViewController, with: typedContext)
42 | }
43 |
44 | var description: String {
45 | String(describing: contextTask)
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/ContextTransformerBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // ContextTransformerBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | final class ContextTransformerBox: AnyContextTransformer {
16 |
17 | let transformer: T
18 |
19 | init(_ transformer: T) {
20 | self.transformer = transformer
21 | }
22 |
23 | func transform(_ context: AnyContext) throws -> Context {
24 | let typedContext: T.SourceContext = try context.value()
25 | guard let transformedContext = try transformer.transform(typedContext) as? Context else {
26 | throw RoutingError.typeMismatch(type: type(of: T.TargetContext.self),
27 | expectedType: Context.self,
28 | .init("Failed to transform \(String(describing: T.TargetContext.self)) to \(String(describing: Context.self))."))
29 | }
30 | return transformedContext
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/FactoryBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // FactoryBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | @MainActor
17 | struct FactoryBox: PreparableAnyFactory, AnyFactoryBox, @preconcurrency CustomStringConvertible {
18 |
19 | typealias FactoryType = F
20 |
21 | var factory: F
22 |
23 | let action: AnyAction
24 |
25 | var isPrepared = false
26 |
27 | init?(_ factory: F, action: AnyAction) {
28 | guard !(factory is NilEntity) else {
29 | return nil
30 | }
31 | self.factory = factory
32 | self.action = action
33 | }
34 |
35 | func build(with context: AnyContext) throws -> UIViewController {
36 | let typedContext: FactoryType.Context = try context.value()
37 | assertIfNotPrepared()
38 | return try factory.build(with: typedContext)
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/FinderBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // FinderBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | struct FinderBox: AnyFinder, @preconcurrency CustomStringConvertible {
17 |
18 | let finder: F
19 |
20 | init?(_ finder: F) {
21 | guard !(finder is NilEntity) else {
22 | return nil
23 | }
24 | self.finder = finder
25 | }
26 |
27 | func findViewController(with context: AnyContext) throws -> UIViewController? {
28 | let typedContext: F.Context = try context.value()
29 | return try finder.findViewController(with: typedContext)
30 | }
31 |
32 | var description: String {
33 | String(describing: finder)
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/PostRoutingTaskBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // PostRoutingTaskBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | struct PostRoutingTaskBox: AnyPostRoutingTask, @preconcurrency CustomStringConvertible {
17 |
18 | let postRoutingTask: PT
19 |
20 | init(_ postRoutingTask: PT) {
21 | self.postRoutingTask = postRoutingTask
22 | }
23 |
24 | func perform(on viewController: UIViewController,
25 | with context: AnyContext,
26 | routingStack: [UIViewController]) throws {
27 | guard let typedViewController = viewController as? PT.ViewController else {
28 | throw RoutingError.typeMismatch(type: type(of: viewController),
29 | expectedType: PT.ViewController.self,
30 | .init("\(String(describing: postRoutingTask.self)) does not support \(String(describing: viewController.self))."))
31 | }
32 | let typedDestination: PT.Context = try context.value()
33 | postRoutingTask.perform(on: typedViewController, with: typedDestination, routingStack: routingStack)
34 | }
35 |
36 | var description: String {
37 | String(describing: postRoutingTask)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Router/Type Erasure/Boxes/RoutingInterceptorBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // RoutingInterceptorBox.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | struct RoutingInterceptorBox: AnyRoutingInterceptor, PreparableEntity, @preconcurrency CustomStringConvertible {
16 |
17 | var routingInterceptor: RI
18 |
19 | var isPrepared = false
20 |
21 | init(_ routingInterceptor: RI) {
22 | self.routingInterceptor = routingInterceptor
23 | }
24 |
25 | mutating func prepare(with context: AnyContext) throws {
26 | let typedDestination: RI.Context = try context.value()
27 | try routingInterceptor.prepare(with: typedDestination)
28 | isPrepared = true
29 | }
30 |
31 | func perform(with context: AnyContext, completion: @escaping (RoutingResult) -> Void) {
32 | do {
33 | let typedContext: RI.Context = try context.value()
34 | assertIfNotPrepared()
35 | routingInterceptor.perform(with: typedContext) { result in
36 | completion(result)
37 | }
38 | } catch {
39 | completion(.failure(error))
40 | }
41 |
42 | }
43 |
44 | var description: String {
45 | String(describing: routingInterceptor)
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/RoutingInterceptable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // RoutingInterceptable.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 | import UIKit
15 |
16 | /// `UIViewController` that conforms to this protocol may overtake the control of the view controllers stack and
17 | /// forbid the `Router` to dismiss or cover itself with another view controller.
18 | /// Return false if the view controller can be dismissed.
19 | @MainActor
20 | public protocol RoutingInterceptable where Self: UIViewController {
21 |
22 | // MARK: Properties to implement
23 |
24 | /// true: if a view controller can be dismissed or covered by the `Router`, false otherwise.
25 | var canBeDismissed: Bool { get }
26 |
27 | /// Returns `UIViewController` that `Router` should consider as a parent `UIViewController`.
28 | /// It may be useful to override it when you are building complicated custom `ContainerViewController`s.
29 | var overriddenParentViewController: UIViewController? { get }
30 |
31 | }
32 |
33 | // MARK: Helper methods
34 |
35 | @MainActor
36 | public extension RoutingInterceptable {
37 |
38 | /// Default implementation returns regular `UIViewController.parent`
39 | var overriddenParentViewController: UIViewController? {
40 | return parent
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/RoutingResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // RoutingResult.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import Foundation
14 |
15 | /// The result of the navigation process
16 | ///
17 | /// - success: The request to process the navigation resulted in a successful navigation to the destination.
18 | /// - failure: The request to process the navigation was not successful.
19 | public enum RoutingResult: Sendable {
20 |
21 | /// The request to process the navigation resulted in a successful navigation to the destination.
22 | case success
23 |
24 | /// The request to process the navigation was not successful.
25 | case failure(Error)
26 |
27 | }
28 |
29 | // MARK: Helper methods
30 |
31 | public extension RoutingResult {
32 |
33 | /// Returns `true` if `RoutingResult` is `success`
34 | var isSuccessful: Bool {
35 | guard case .success = self else {
36 | return false
37 | }
38 | return true
39 | }
40 |
41 | /// Returns SDK's `Result` value.
42 | var swiftResult: Result {
43 | switch self {
44 | case .success:
45 | return .success(())
46 | case let .failure(error):
47 | return .failure(error)
48 | }
49 | }
50 |
51 | /// Returns the `Error` instance of the `RoutingResult`.
52 | /// - Throws: The `RoutingError` if `RoutingResult` is `success`.
53 | func getError() throws -> Error {
54 | guard case let .failure(error) = self else {
55 | throw RoutingError.generic(.init("Navigation is successful"))
56 | }
57 | return error
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Steps/NavigationControllerStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // NavigationControllerStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// Default navigation container step
16 | public final class NavigationControllerStep: SingleContainerStep, NavigationControllerFactory> {
17 |
18 | // MARK: Methods
19 |
20 | /// Constructor
21 | public init() {
22 | super.init(finder: NilFinder(), factory: NavigationControllerFactory())
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Steps/SplitControllerStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // SplitControllerStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// Default split container step
16 | public final class SplitControllerStep: SingleContainerStep, SplitControllerFactory> {
17 |
18 | // MARK: Methods
19 |
20 | /// Constructor.
21 | public init() {
22 | super.init(finder: NilFinder(), factory: SplitControllerFactory())
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/RouteComposer/Classes/Steps/TabBarControllerStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer
3 | // TabBarControllerStep.swift
4 | // https://github.com/ekazaev/route-composer
5 | //
6 | // Created by Eugene Kazaev in 2018-2025.
7 | // Distributed under the MIT license.
8 | //
9 | // Become a sponsor:
10 | // https://github.com/sponsors/ekazaev
11 | //
12 |
13 | import UIKit
14 |
15 | /// Default tab bar container step
16 | public final class TabBarControllerStep: SingleContainerStep, TabBarControllerFactory> {
17 |
18 | // MARK: Methods
19 |
20 | /// Constructor
21 | public init() {
22 | super.init(finder: NilFinder(), factory: TabBarControllerFactory())
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/RouteComposer/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/RouteComposer/RouteComposer.h:
--------------------------------------------------------------------------------
1 | //
2 | // RouteComposer.h
3 | // RouteComposer
4 | //
5 | // Created by Eugene Kazaev on 01/03/2020.
6 | // Copyright © 2020 Eugene Kazaev. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for RouteComposer.
12 | FOUNDATION_EXPORT double RouteComposerVersionNumber;
13 |
14 | //! Project version string for RouteComposer.
15 | FOUNDATION_EXPORT const unsigned char RouteComposerVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------
/docs/badge.svg:
--------------------------------------------------------------------------------
1 |
29 |
--------------------------------------------------------------------------------
/docs/docsets/RouteComposer.docset/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIdentifier
6 | com.jazzy.routecomposer
7 | CFBundleName
8 | RouteComposer
9 | DocSetPlatformFamily
10 | routecomposer
11 | isDashDocset
12 |
13 | dashIndexFilePath
14 | index.html
15 | isJavaScriptEnabled
16 |
17 | DashDocSetFamily
18 | dashtoc
19 |
20 |
21 |
--------------------------------------------------------------------------------
/docs/docsets/RouteComposer.docset/Contents/Resources/Documents/img/carat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/docsets/RouteComposer.docset/Contents/Resources/Documents/img/carat.png
--------------------------------------------------------------------------------
/docs/docsets/RouteComposer.docset/Contents/Resources/Documents/img/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/docsets/RouteComposer.docset/Contents/Resources/Documents/img/dash.png
--------------------------------------------------------------------------------
/docs/docsets/RouteComposer.docset/Contents/Resources/Documents/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/docsets/RouteComposer.docset/Contents/Resources/Documents/img/spinner.gif
--------------------------------------------------------------------------------
/docs/docsets/RouteComposer.docset/Contents/Resources/docSet.dsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/docsets/RouteComposer.docset/Contents/Resources/docSet.dsidx
--------------------------------------------------------------------------------
/docs/docsets/RouteComposer.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/docsets/RouteComposer.tgz
--------------------------------------------------------------------------------
/docs/img/carat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/img/carat.png
--------------------------------------------------------------------------------
/docs/img/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/img/dash.png
--------------------------------------------------------------------------------
/docs/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/img/spinner.gif
--------------------------------------------------------------------------------
/docs/tests/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekazaev/route-composer/a42ce29363b82e29793611fdb49199d615398121/docs/tests/logo.jpg
--------------------------------------------------------------------------------
/docs/undocumented.json:
--------------------------------------------------------------------------------
1 | {
2 | "warnings": [
3 |
4 | ],
5 | "source_directory": "/Users/ekazaev/Workplace/route-composer"
6 | }
--------------------------------------------------------------------------------