├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 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 | } --------------------------------------------------------------------------------