├── str.jpg ├── ApplicationCoordinator ├── Application │ ├── ApplicationCoordinator-Bridging-Header.h │ ├── AppDelegate.swift │ └── ApplicationCoordinator.swift ├── Flows │ ├── Items flow │ │ ├── ItemDetailView.swift │ │ ├── ItemsListView.swift │ │ ├── ItemDetailController.swift │ │ ├── ItemCoordinator.swift │ │ └── ItemsListController.swift │ ├── Settings flow │ │ ├── SettingsView.swift │ │ ├── SettingsCoordinator.swift │ │ └── SettingsController.swift │ ├── OnboardingFlow │ │ ├── Controllers │ │ │ ├── OnboardingView.swift │ │ │ ├── OnboardingController.swift │ │ │ ├── StepController.swift │ │ │ └── PageViewController.swift │ │ ├── OnboardingCoordinatorOutput.swift │ │ └── OnboardingCoordinator.swift │ ├── Login flow │ │ ├── AuthCoordinatorOutput.swift │ │ ├── LoginView.swift │ │ ├── Controllers │ │ │ ├── TermsView.swift │ │ │ └── TermsController.swift │ │ ├── SignUpView.swift │ │ ├── LoginController.swift │ │ ├── SignUpController.swift │ │ └── AuthCoordinator.swift │ ├── Create flow │ │ ├── ItemCreateCoordinatorOutput.swift │ │ ├── ItemCreateView.swift │ │ ├── ItemCreateController.swift │ │ └── ItemCreateCoordinator.swift │ └── MainTabbarFlow │ │ ├── TabbarView.swift │ │ ├── TabbarController.swift │ │ └── TabbarCoordinator.swift ├── Protocols │ ├── BaseView.swift │ ├── Coordinator.swift │ ├── Presentable.swift │ └── DeepLinkOption.swift ├── Model │ ├── Setting.swift │ └── ItemList.swift ├── Factories │ ├── SettingsModuleFactory.swift │ ├── ItemCreateModuleFactory.swift │ ├── OnboardingModuleFactory.swift │ ├── ItemModuleFactory.swift │ ├── AuthModuleFactory.swift │ ├── CoordinatorFactory.swift │ ├── ModuleFactoryImp.swift │ └── CoordinatorFactoryImp.swift ├── Images.xcassets │ ├── Brand Assets.launchimage │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Extensions │ ├── NSObjectExtensions.swift │ ├── UIViewExtensions.swift │ └── UIViewControllerExtension.swift ├── Storyboards │ ├── StoryboardsEnum.swift │ ├── Create.storyboard │ ├── Settings.storyboard │ ├── Onboarding.storyboard │ ├── Items.storyboard │ └── Auth.storyboard ├── Presenters │ ├── Router.swift │ └── RouterImp.swift ├── Parents │ └── BaseCoordinator.swift ├── Resources │ └── Info.plist └── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ApplicationCoordinator.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── ApplicationCoordinatorTests ├── Info.plist ├── SettingsFlow │ └── SettingsCoordinatorTest.swift ├── Parent │ └── BaseCoordinatorTest.swift ├── ItemsFlow │ └── ItemCoordinatorTest.swift ├── Router │ ├── RouterTest.swift │ └── RouterMock.swift └── LoginFlow │ └── AuthCoordinatorTest.swift ├── LICENSE ├── .gitignore └── README.md /str.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreyPanov/ApplicationCoordinator/HEAD/str.jpg -------------------------------------------------------------------------------- /ApplicationCoordinator/Application/ApplicationCoordinator-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | @import UIKit; 2 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Items flow/ItemDetailView.swift: -------------------------------------------------------------------------------- 1 | protocol ItemDetailView: BaseView { } 2 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Settings flow/SettingsView.swift: -------------------------------------------------------------------------------- 1 | protocol SettingsView: BaseView { } 2 | 3 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Protocols/BaseView.swift: -------------------------------------------------------------------------------- 1 | protocol BaseView: NSObjectProtocol, Presentable { } 2 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Model/Setting.swift: -------------------------------------------------------------------------------- 1 | struct Setting { 2 | 3 | let title: String 4 | let subtitle: String 5 | } 6 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Model/ItemList.swift: -------------------------------------------------------------------------------- 1 | struct ItemList { 2 | 3 | let title: String 4 | let subtitle: String 5 | } 6 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/SettingsModuleFactory.swift: -------------------------------------------------------------------------------- 1 | protocol SettingsModuleFactory { 2 | func makeSettingsOutput() -> SettingsView 3 | } 4 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/ItemCreateModuleFactory.swift: -------------------------------------------------------------------------------- 1 | protocol ItemCreateModuleFactory { 2 | func makeItemAddOutput() -> ItemCreateView 3 | } 4 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/OnboardingModuleFactory.swift: -------------------------------------------------------------------------------- 1 | protocol OnboardingModuleFactory { 2 | func makeOnboardingModule() -> OnboardingView 3 | } 4 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Protocols/Coordinator.swift: -------------------------------------------------------------------------------- 1 | protocol Coordinator: AnyObject { 2 | func start() 3 | func start(with option: DeepLinkOption?) 4 | } 5 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/OnboardingFlow/Controllers/OnboardingView.swift: -------------------------------------------------------------------------------- 1 | protocol OnboardingView: BaseView { 2 | var onFinish: (() -> Void)? { get set } 3 | } 4 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/AuthCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | protocol AuthCoordinatorOutput: AnyObject { 2 | var finishFlow: (() -> Void)? { get set } 3 | } 4 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Create flow/ItemCreateCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | protocol ItemCreateCoordinatorOutput: AnyObject { 2 | var finishFlow: ((ItemList?)->())? { get set } 3 | } 4 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/OnboardingFlow/OnboardingCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | protocol OnboardingCoordinatorOutput: AnyObject { 2 | var finishFlow: (() -> Void)? { get set } 3 | } 4 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Images.xcassets/Brand Assets.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | 4 | ], 5 | "info" : { 6 | "version" : 1, 7 | "author" : "xcode" 8 | } 9 | } -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/LoginView.swift: -------------------------------------------------------------------------------- 1 | protocol LoginView: BaseView { 2 | var onCompleteAuth: (() -> Void)? { get set } 3 | var onSignUpButtonTap: (() -> Void)? { get set } 4 | } 5 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/ItemModuleFactory.swift: -------------------------------------------------------------------------------- 1 | protocol ItemModuleFactory { 2 | func makeItemsOutput() -> ItemsListView 3 | func makeItemDetailOutput(item: ItemList) -> ItemDetailView 4 | } 5 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Items flow/ItemsListView.swift: -------------------------------------------------------------------------------- 1 | protocol ItemsListView: BaseView { 2 | var onItemSelect: ((ItemList) -> ())? { get set } 3 | var onCreateItem: (() -> Void)? { get set } 4 | } 5 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/Controllers/TermsView.swift: -------------------------------------------------------------------------------- 1 | protocol TermsView: BaseView { 2 | 3 | var confirmed: Bool { get set } 4 | var onConfirmChanged: ((Bool) -> ())? { get set } 5 | } 6 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Extensions/NSObjectExtensions.swift: -------------------------------------------------------------------------------- 1 | extension NSObject { 2 | 3 | class var nameOfClass: String { 4 | return NSStringFromClass(self).components(separatedBy: ".").last! 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/AuthModuleFactory.swift: -------------------------------------------------------------------------------- 1 | protocol AuthModuleFactory { 2 | func makeLoginOutput() -> LoginView 3 | func makeSignUpHandler() -> SignUpView 4 | func makeTermsOutput() -> TermsView 5 | } 6 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Create flow/ItemCreateView.swift: -------------------------------------------------------------------------------- 1 | protocol ItemCreateView: BaseView { 2 | var onHideButtonTap: (() -> Void)? { get set } 3 | var onCompleteCreateItem: ((ItemList) -> ())? { get set } 4 | } 5 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Storyboards/StoryboardsEnum.swift: -------------------------------------------------------------------------------- 1 | enum Storyboards: String { 2 | case main = "Main" 3 | case items = "Items" 4 | case auth = "Auth" 5 | case onboarding = "Onboarding" 6 | case create = "Create" 7 | case settings = "Settings" 8 | } 9 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Protocols/Presentable.swift: -------------------------------------------------------------------------------- 1 | protocol Presentable { 2 | func toPresent() -> UIViewController? 3 | } 4 | 5 | extension UIViewController: Presentable { 6 | 7 | func toPresent() -> UIViewController? { 8 | return self 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/SignUpView.swift: -------------------------------------------------------------------------------- 1 | protocol SignUpView: BaseView { 2 | 3 | var confirmed: Bool { get set } 4 | var onSignUpComplete: (() -> Void)? { get set } 5 | var onTermsButtonTap: (() -> Void)? { get set } 6 | 7 | func conformTermsAgreement(_ agree: Bool) 8 | } 9 | -------------------------------------------------------------------------------- /ApplicationCoordinator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/MainTabbarFlow/TabbarView.swift: -------------------------------------------------------------------------------- 1 | protocol TabbarView: AnyObject { 2 | var onItemFlowSelect: ((UINavigationController) -> ())? { get set } 3 | var onSettingsFlowSelect: ((UINavigationController) -> ())? { get set } 4 | var onViewDidLoad: ((UINavigationController) -> ())? { get set } 5 | } 6 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Items flow/ItemDetailController.swift: -------------------------------------------------------------------------------- 1 | final class ItemDetailController: UIViewController, ItemDetailView { 2 | 3 | //controller handler 4 | var itemList: ItemList? 5 | 6 | override func viewDidLoad() { 7 | super.viewDidLoad() 8 | 9 | title = itemList?.title ?? "Detail" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ApplicationCoordinator.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/OnboardingFlow/Controllers/OnboardingController.swift: -------------------------------------------------------------------------------- 1 | class OnboardingController: UIViewController, OnboardingView { 2 | 3 | var onFinish: (() -> Void)? 4 | 5 | override func viewDidLoad() { 6 | super.viewDidLoad() 7 | } 8 | 9 | @IBAction func finishTapped(_ sender: Any) { 10 | onFinish?() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/Controllers/TermsController.swift: -------------------------------------------------------------------------------- 1 | class TermsController: UIViewController, TermsView { 2 | 3 | @IBOutlet weak var termsSwitch: UISwitch! { 4 | didSet { termsSwitch.isOn = confirmed } 5 | } 6 | var confirmed = false 7 | var onConfirmChanged: ((Bool) -> ())? 8 | 9 | @IBAction func termsSwitchValueChanged(_ sender: UISwitch) { 10 | onConfirmChanged?(sender.isOn) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/OnboardingFlow/Controllers/StepController.swift: -------------------------------------------------------------------------------- 1 | class StepController: UIViewController { 2 | 3 | @IBOutlet weak var titleLabel: UILabel! 4 | var titleText: String? { 5 | get { return titleLabel.text } 6 | set { titleLabel.text = newValue } 7 | } 8 | 9 | override func viewDidLoad() { 10 | super.viewDidLoad() 11 | } 12 | 13 | @IBAction func skipTapped(_ sender: Any) { 14 | } 15 | 16 | @IBAction func nextTapped(_ sender: Any) { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Extensions/UIViewExtensions.swift: -------------------------------------------------------------------------------- 1 | extension UIView { 2 | 3 | private class func viewInNibNamed(_ nibNamed: String) -> T { 4 | return Bundle.main.loadNibNamed(nibNamed, owner: nil, options: nil)!.first as! T 5 | } 6 | 7 | class func nib() -> Self { 8 | return viewInNibNamed(nameOfClass) 9 | } 10 | 11 | class func nib(_ frame: CGRect) -> Self { 12 | let view = nib() 13 | view.frame = frame 14 | view.layoutIfNeeded() 15 | return view 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/LoginController.swift: -------------------------------------------------------------------------------- 1 | final class LoginController: UIViewController, LoginView { 2 | 3 | //controller handler 4 | var onCompleteAuth: (() -> Void)? 5 | var onSignUpButtonTap: (() -> Void)? 6 | 7 | @IBAction func loginButtonClicked(_ sender: AnyObject) { onCompleteAuth?() } 8 | @IBAction func signUpClicked(_ sender: AnyObject) { onSignUpButtonTap?() } 9 | 10 | override func viewDidLoad() { 11 | super.viewDidLoad() 12 | 13 | title = "Login" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Settings flow/SettingsCoordinator.swift: -------------------------------------------------------------------------------- 1 | final class SettingsCoordinator: BaseCoordinator { 2 | 3 | private let factory: SettingsModuleFactory 4 | private let router: Router 5 | 6 | init(router: Router, factory: SettingsModuleFactory) { 7 | self.factory = factory 8 | self.router = router 9 | } 10 | 11 | override func start() { 12 | showSettings() 13 | } 14 | 15 | //MARK: - Run current flow's controllers 16 | 17 | private func showSettings() { 18 | let settingsFlowOutput = factory.makeSettingsOutput() 19 | router.setRootModule(settingsFlowOutput) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/OnboardingFlow/OnboardingCoordinator.swift: -------------------------------------------------------------------------------- 1 | class OnboardingCoordinator: BaseCoordinator, OnboardingCoordinatorOutput { 2 | 3 | var finishFlow: (() -> Void)? 4 | 5 | private let factory: OnboardingModuleFactory 6 | private let router: Router 7 | 8 | init(with factory: OnboardingModuleFactory, router: Router) { 9 | self.factory = factory 10 | self.router = router 11 | } 12 | 13 | override func start() { 14 | showOnboarding() 15 | } 16 | 17 | func showOnboarding() { 18 | let onboardingModule = factory.makeOnboardingModule() 19 | onboardingModule.onFinish = { [weak self] in 20 | self?.finishFlow?() 21 | } 22 | router.setRootModule(onboardingModule.toPresent()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Create flow/ItemCreateController.swift: -------------------------------------------------------------------------------- 1 | final class ItemCreateController: UIViewController, ItemCreateView { 2 | 3 | //controller handler 4 | var onHideButtonTap: (() -> Void)? 5 | var onCompleteCreateItem: ((ItemList) -> ())? 6 | 7 | override func viewDidLoad() { 8 | super.viewDidLoad() 9 | 10 | title = "Create" 11 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Hide", style: .plain, target: self, action: #selector(ItemCreateController.hideButtonClicked(_:))) 12 | } 13 | 14 | @IBAction func hideButtonClicked(_ sender: UIBarButtonItem) { 15 | onHideButtonTap?() 16 | } 17 | 18 | @IBAction func createButtonClicked(_ sender: UIBarButtonItem) { 19 | onCompleteCreateItem?(ItemList(title: "", subtitle: "")) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ApplicationCoordinatorTests/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 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Presenters/Router.swift: -------------------------------------------------------------------------------- 1 | protocol Router: Presentable { 2 | 3 | func present(_ module: Presentable?) 4 | func present(_ module: Presentable?, animated: Bool) 5 | 6 | func push(_ module: Presentable?) 7 | func push(_ module: Presentable?, hideBottomBar: Bool) 8 | func push(_ module: Presentable?, animated: Bool) 9 | func push(_ module: Presentable?, animated: Bool, completion: (() -> Void)?) 10 | func push(_ module: Presentable?, animated: Bool, hideBottomBar: Bool, completion: (() -> Void)?) 11 | 12 | func popModule() 13 | func popModule(animated: Bool) 14 | 15 | func dismissModule() 16 | func dismissModule(animated: Bool, completion: (() -> Void)?) 17 | 18 | func setRootModule(_ module: Presentable?) 19 | func setRootModule(_ module: Presentable?, hideBar: Bool) 20 | 21 | func popToRootModule(animated: Bool) 22 | } 23 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Extensions/UIViewControllerExtension.swift: -------------------------------------------------------------------------------- 1 | extension UIViewController { 2 | 3 | private class func instantiateControllerInStoryboard(_ storyboard: UIStoryboard, identifier: String) -> T { 4 | return storyboard.instantiateViewController(withIdentifier: identifier) as! T 5 | } 6 | 7 | class func controllerInStoryboard(_ storyboard: UIStoryboard, identifier: String) -> Self { 8 | return instantiateControllerInStoryboard(storyboard, identifier: identifier) 9 | } 10 | 11 | class func controllerInStoryboard(_ storyboard: UIStoryboard) -> Self { 12 | return controllerInStoryboard(storyboard, identifier: nameOfClass) 13 | } 14 | 15 | class func controllerFromStoryboard(_ storyboard: Storyboards) -> Self { 16 | return controllerInStoryboard(UIStoryboard(name: storyboard.rawValue, bundle: nil), identifier: nameOfClass) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Create flow/ItemCreateCoordinator.swift: -------------------------------------------------------------------------------- 1 | final class ItemCreateCoordinator: BaseCoordinator, ItemCreateCoordinatorOutput { 2 | 3 | var finishFlow: ((ItemList?)->())? 4 | 5 | private let factory: ItemCreateModuleFactory 6 | private let router: Router 7 | 8 | init(router: Router, factory: ItemCreateModuleFactory) { 9 | self.factory = factory 10 | self.router = router 11 | } 12 | 13 | override func start() { 14 | showCreate() 15 | } 16 | 17 | //MARK: - Run current flow's controllers 18 | 19 | private func showCreate() { 20 | let createItemOutput = factory.makeItemAddOutput() 21 | createItemOutput.onCompleteCreateItem = { [weak self] item in 22 | self?.finishFlow?(item) 23 | } 24 | createItemOutput.onHideButtonTap = { [weak self] in 25 | self?.finishFlow?(nil) 26 | } 27 | router.setRootModule(createItemOutput) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/CoordinatorFactory.swift: -------------------------------------------------------------------------------- 1 | protocol CoordinatorFactory { 2 | 3 | func makeTabbarCoordinator() -> (configurator: Coordinator, toPresent: Presentable?) 4 | func makeAuthCoordinatorBox(router: Router) -> Coordinator & AuthCoordinatorOutput 5 | 6 | func makeOnboardingCoordinator(router: Router) -> Coordinator & OnboardingCoordinatorOutput 7 | 8 | func makeItemCoordinator(navController: UINavigationController?) -> Coordinator 9 | func makeItemCoordinator() -> Coordinator 10 | 11 | func makeSettingsCoordinator() -> Coordinator 12 | func makeSettingsCoordinator(navController: UINavigationController?) -> Coordinator 13 | 14 | func makeItemCreationCoordinatorBox() -> 15 | (configurator: Coordinator & ItemCreateCoordinatorOutput, 16 | toPresent: Presentable?) 17 | 18 | func makeItemCreationCoordinatorBox(navController: UINavigationController?) -> 19 | (configurator: Coordinator & ItemCreateCoordinatorOutput, 20 | toPresent: Presentable?) 21 | } 22 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/MainTabbarFlow/TabbarController.swift: -------------------------------------------------------------------------------- 1 | final class TabbarController: UITabBarController, UITabBarControllerDelegate, TabbarView { 2 | 3 | var onItemFlowSelect: ((UINavigationController) -> ())? 4 | var onSettingsFlowSelect: ((UINavigationController) -> ())? 5 | var onViewDidLoad: ((UINavigationController) -> ())? 6 | 7 | override func viewDidLoad() { 8 | super.viewDidLoad() 9 | 10 | delegate = self 11 | if let controller = customizableViewControllers?.first as? UINavigationController { 12 | onViewDidLoad?(controller) 13 | } 14 | } 15 | 16 | func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { 17 | guard let controller = viewControllers?[selectedIndex] as? UINavigationController else { return } 18 | 19 | if selectedIndex == 0 { 20 | onItemFlowSelect?(controller) 21 | } 22 | else if selectedIndex == 1 { 23 | onSettingsFlowSelect?(controller) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/SignUpController.swift: -------------------------------------------------------------------------------- 1 | final class SignUpController: UIViewController, SignUpView { 2 | 3 | //controller handler 4 | var onSignUpComplete: (() -> Void)? 5 | var onTermsButtonTap: (() -> Void)? 6 | 7 | @IBOutlet weak var termsLabel: UILabel! 8 | @IBOutlet weak var signUpButton: UIButton! 9 | 10 | var confirmed = false { 11 | didSet { 12 | termsLabel.isHidden = !confirmed 13 | signUpButton.isEnabled = confirmed 14 | } 15 | } 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | title = "SignUp" 21 | termsLabel.isHidden = true 22 | signUpButton.isEnabled = false 23 | } 24 | 25 | @IBAction func signUpClicked(_ sender: AnyObject) { 26 | if confirmed { 27 | onSignUpComplete?() 28 | } 29 | } 30 | 31 | @IBAction func termsButtonClicked(_ sender: AnyObject) { 32 | onTermsButtonTap?() 33 | } 34 | 35 | func conformTermsAgreement(_ agree: Bool) { 36 | confirmed = agree 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ApplicationCoordinatorTests/SettingsFlow/SettingsCoordinatorTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsCoordinatorTest.swift 3 | // ApplicationCoordinator 4 | // 5 | // Created by Andrey on 04.09.16. 6 | // Copyright © 2016 Andrey Panov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ApplicationCoordinator 11 | 12 | class SettingsCoordinatorTest: XCTestCase { 13 | 14 | private var coordinator: Coordinator! 15 | private var router: RouterMock! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | router = RouterMockImp() 21 | coordinator = SettingsCoordinator(router: router, factory: ModuleFactoryImp()) 22 | } 23 | 24 | override func tearDown() { 25 | coordinator = nil 26 | router = nil 27 | 28 | super.tearDown() 29 | } 30 | 31 | func testStart() { 32 | coordinator.start() 33 | // after start() call coordinator must push SettingsController 34 | XCTAssertTrue(router.navigationStack.first is SettingsController) 35 | XCTAssertTrue(router.navigationStack.count == 1) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andrey Panov 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 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Parents/BaseCoordinator.swift: -------------------------------------------------------------------------------- 1 | class BaseCoordinator: Coordinator { 2 | 3 | var childCoordinators: [Coordinator] = [] 4 | 5 | func start() { 6 | start(with: nil) 7 | } 8 | 9 | func start(with option: DeepLinkOption?) { } 10 | 11 | // add only unique object 12 | func addDependency(_ coordinator: Coordinator) { 13 | guard !childCoordinators.contains(where: { $0 === coordinator }) else { return } 14 | childCoordinators.append(coordinator) 15 | } 16 | 17 | func removeDependency(_ coordinator: Coordinator?) { 18 | guard 19 | childCoordinators.isEmpty == false, 20 | let coordinator = coordinator 21 | else { return } 22 | 23 | // Clear child-coordinators recursively 24 | if let coordinator = coordinator as? BaseCoordinator, !coordinator.childCoordinators.isEmpty { 25 | coordinator.childCoordinators 26 | .filter({ $0 !== coordinator }) 27 | .forEach({ coordinator.removeDependency($0) }) 28 | } 29 | for (index, element) in childCoordinators.enumerated() where element === coordinator { 30 | childCoordinators.remove(at: index) 31 | break 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/OnboardingFlow/Controllers/PageViewController.swift: -------------------------------------------------------------------------------- 1 | class PageController: UIViewController { 2 | 3 | var pageViewController: UIPageViewController! 4 | 5 | override func viewDidLoad() { 6 | super.viewDidLoad() 7 | 8 | pageViewController = UIPageViewController( 9 | transitionStyle: .scroll, 10 | navigationOrientation: .horizontal, 11 | options: nil 12 | ) 13 | pageViewController.delegate = self 14 | addChild(pageViewController) 15 | view.addSubview(pageViewController.view) 16 | } 17 | } 18 | 19 | extension PageController: UIPageViewControllerDelegate, UIPageViewControllerDataSource { 20 | 21 | func pageViewController(_ pageViewController: UIPageViewController, 22 | viewControllerBefore viewController: UIViewController) -> UIViewController? { 23 | return UIViewController() 24 | } 25 | 26 | func pageViewController(_ pageViewController: UIPageViewController, 27 | viewControllerAfter viewController: UIViewController) -> UIViewController? { 28 | return UIViewController() 29 | } 30 | 31 | func presentationCount(for pageViewController: UIPageViewController) -> Int { 32 | return 3 33 | } 34 | 35 | func presentationIndex(for pageViewController: UIPageViewController) -> Int { 36 | return 2 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/ModuleFactoryImp.swift: -------------------------------------------------------------------------------- 1 | final class ModuleFactoryImp: 2 | AuthModuleFactory, 3 | OnboardingModuleFactory, 4 | ItemModuleFactory, 5 | ItemCreateModuleFactory, 6 | SettingsModuleFactory { 7 | 8 | func makeLoginOutput() -> LoginView { 9 | return LoginController.controllerFromStoryboard(.auth) 10 | } 11 | 12 | func makeSignUpHandler() -> SignUpView { 13 | return SignUpController.controllerFromStoryboard(.auth) 14 | } 15 | 16 | func makeOnboardingModule() -> OnboardingView { 17 | return OnboardingController.controllerFromStoryboard(.onboarding) 18 | } 19 | 20 | func makeTermsOutput() -> TermsView { 21 | return TermsController.controllerFromStoryboard(.auth) 22 | } 23 | 24 | func makeItemsOutput() -> ItemsListView { 25 | return ItemsListController.controllerFromStoryboard(.items) 26 | } 27 | 28 | func makeItemDetailOutput(item: ItemList) -> ItemDetailView { 29 | 30 | let controller = ItemDetailController.controllerFromStoryboard(.items) 31 | controller.itemList = item 32 | return controller 33 | } 34 | 35 | func makeItemAddOutput() -> ItemCreateView { 36 | return ItemCreateController.controllerFromStoryboard(.create) 37 | } 38 | 39 | func makeSettingsOutput() -> SettingsView { 40 | return SettingsController.controllerFromStoryboard(.settings) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/MainTabbarFlow/TabbarCoordinator.swift: -------------------------------------------------------------------------------- 1 | class TabbarCoordinator: BaseCoordinator { 2 | 3 | private let tabbarView: TabbarView 4 | private let coordinatorFactory: CoordinatorFactory 5 | 6 | init(tabbarView: TabbarView, coordinatorFactory: CoordinatorFactory) { 7 | self.tabbarView = tabbarView 8 | self.coordinatorFactory = coordinatorFactory 9 | } 10 | 11 | override func start() { 12 | tabbarView.onViewDidLoad = runItemFlow() 13 | tabbarView.onItemFlowSelect = runItemFlow() 14 | tabbarView.onSettingsFlowSelect = runSettingsFlow() 15 | } 16 | 17 | private func runItemFlow() -> ((UINavigationController) -> ()) { 18 | return { [unowned self] navController in 19 | if navController.viewControllers.isEmpty == true { 20 | let itemCoordinator = self.coordinatorFactory.makeItemCoordinator(navController: navController) 21 | self.addDependency(itemCoordinator) 22 | itemCoordinator.start() 23 | } 24 | } 25 | } 26 | 27 | private func runSettingsFlow() -> ((UINavigationController) -> ()) { 28 | return { [unowned self] navController in 29 | if navController.viewControllers.isEmpty == true { 30 | let settingsCoordinator = self.coordinatorFactory.makeSettingsCoordinator(navController: navController) 31 | self.addDependency(settingsCoordinator) 32 | settingsCoordinator.start() 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIcons 10 | 11 | CFBundleIcons~ipad 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1 27 | LSRequiresIPhoneOS 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Settings flow/SettingsController.swift: -------------------------------------------------------------------------------- 1 | final class SettingsController: UIViewController, SettingsView { 2 | 3 | //controller handler 4 | 5 | @IBOutlet weak var tableView: UITableView! 6 | //mock datasource 7 | var settings: [[Setting]] = [[]] 8 | 9 | override func viewDidLoad() { 10 | super.viewDidLoad() 11 | 12 | title = "Settings" 13 | makeMockData() 14 | } 15 | 16 | func makeMockData() { 17 | 18 | let settings1 = (0...3).map { index in return Setting(title: "Setting № \(index)", subtitle: "Setting descripton") } 19 | let settings2 = (0...5).map { index in return Setting(title: "Setting № \(index)", subtitle: "Setting descripton") } 20 | settings.append(settings1) 21 | settings.append(settings2) 22 | } 23 | } 24 | 25 | extension SettingsController: UITableViewDelegate, UITableViewDataSource { 26 | 27 | func numberOfSections(in tableView: UITableView) -> Int { 28 | return settings.count 29 | } 30 | 31 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 32 | return settings[section].count 33 | } 34 | 35 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 36 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 37 | 38 | let setting = settings[(indexPath as NSIndexPath).section][(indexPath as NSIndexPath).row] 39 | cell.textLabel?.text = setting.title 40 | cell.detailTextLabel?.text = setting.subtitle 41 | return cell 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Login flow/AuthCoordinator.swift: -------------------------------------------------------------------------------- 1 | final class AuthCoordinator: BaseCoordinator, AuthCoordinatorOutput { 2 | 3 | var finishFlow: (() -> Void)? 4 | 5 | private let factory: AuthModuleFactory 6 | private let router: Router 7 | private weak var signUpView: SignUpView? 8 | 9 | init(router: Router, factory: AuthModuleFactory) { 10 | self.factory = factory 11 | self.router = router 12 | } 13 | 14 | override func start() { 15 | showLogin() 16 | } 17 | 18 | //MARK: - Run current flow's controllers 19 | 20 | private func showLogin() { 21 | let loginOutput = factory.makeLoginOutput() 22 | loginOutput.onCompleteAuth = { [weak self] in 23 | self?.finishFlow?() 24 | } 25 | loginOutput.onSignUpButtonTap = { [weak self] in 26 | self?.showSignUp() 27 | } 28 | router.setRootModule(loginOutput) 29 | } 30 | 31 | private func showSignUp() { 32 | signUpView = factory.makeSignUpHandler() 33 | signUpView?.onSignUpComplete = { [weak self] in 34 | self?.finishFlow?() 35 | } 36 | signUpView?.onTermsButtonTap = { [weak self] in 37 | self?.showTerms() 38 | } 39 | router.push(signUpView) 40 | } 41 | 42 | private func showTerms() { 43 | let termsOutput = factory.makeTermsOutput() 44 | termsOutput.confirmed = self.signUpView?.confirmed ?? false 45 | 46 | termsOutput.onConfirmChanged = { [weak self] confirmed in 47 | self?.signUpView?.conformTermsAgreement(confirmed) 48 | } 49 | router.push(termsOutput, animated: true) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Protocols/DeepLinkOption.swift: -------------------------------------------------------------------------------- 1 | struct DeepLinkURLConstants { 2 | static let Onboarding = "onboarding" 3 | static let Items = "items" 4 | static let Item = "item" 5 | static let Settings = "settings" 6 | static let Login = "login" 7 | static let Terms = "terms" 8 | static let SignUp = "signUp" 9 | } 10 | 11 | enum DeepLinkOption { 12 | 13 | case onboarding 14 | case items 15 | case settings 16 | case login 17 | case terms 18 | case signUp 19 | case item(String?) 20 | 21 | static func build(with userActivity: NSUserActivity) -> DeepLinkOption? { 22 | if userActivity.activityType == NSUserActivityTypeBrowsingWeb, 23 | let url = userActivity.webpageURL, 24 | let _ = URLComponents(url: url, resolvingAgainstBaseURL: true) { 25 | //TODO: extract string and match with DeepLinkURLConstants 26 | } 27 | return nil 28 | } 29 | 30 | static func build(with dict: [String : AnyObject]?) -> DeepLinkOption? { 31 | guard let id = dict?["launch_id"] as? String else { return nil } 32 | 33 | let itemID = dict?["item_id"] as? String 34 | 35 | switch id { 36 | case DeepLinkURLConstants.Onboarding: return .onboarding 37 | case DeepLinkURLConstants.Items: return .items 38 | case DeepLinkURLConstants.Item: return .item(itemID) 39 | case DeepLinkURLConstants.Settings: return .settings 40 | case DeepLinkURLConstants.Login: return .login 41 | case DeepLinkURLConstants.Terms: return .terms 42 | case DeepLinkURLConstants.SignUp: return .signUp 43 | default: return nil 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## macOS 6 | .DS_STORE 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 64 | 65 | fastlane/report.xml 66 | fastlane/screenshots 67 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Items flow/ItemCoordinator.swift: -------------------------------------------------------------------------------- 1 | final class ItemCoordinator: BaseCoordinator { 2 | 3 | private let factory: ItemModuleFactory 4 | private let coordinatorFactory: CoordinatorFactory 5 | private let router: Router 6 | 7 | init(router: Router, factory: ItemModuleFactory, coordinatorFactory: CoordinatorFactory) { 8 | self.router = router 9 | self.factory = factory 10 | self.coordinatorFactory = coordinatorFactory 11 | } 12 | 13 | override func start() { 14 | showItemList() 15 | } 16 | 17 | //MARK: - Run current flow's controllers 18 | 19 | private func showItemList() { 20 | 21 | let itemsOutput = factory.makeItemsOutput() 22 | itemsOutput.onItemSelect = { [weak self] (item) in 23 | self?.showItemDetail(item) 24 | } 25 | itemsOutput.onCreateItem = { [weak self] in 26 | self?.runCreationFlow() 27 | } 28 | router.setRootModule(itemsOutput) 29 | } 30 | 31 | private func showItemDetail(_ item: ItemList) { 32 | 33 | let itemDetailFlowOutput = factory.makeItemDetailOutput(item: item) 34 | router.push(itemDetailFlowOutput, hideBottomBar: true) 35 | } 36 | 37 | //MARK: - Run coordinators (switch to another flow) 38 | 39 | private func runCreationFlow() { 40 | 41 | let (coordinator, module) = coordinatorFactory.makeItemCreationCoordinatorBox() 42 | coordinator.finishFlow = { [weak self, weak coordinator] item in 43 | 44 | self?.router.dismissModule() 45 | self?.removeDependency(coordinator) 46 | if let item = item { 47 | self?.showItemDetail(item) 48 | } 49 | } 50 | addDependency(coordinator) 51 | router.present(module) 52 | coordinator.start() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | @UIApplicationMain 2 | class AppDelegate: UIResponder, UIApplicationDelegate { 3 | 4 | var window: UIWindow? 5 | var rootController: UINavigationController { 6 | return self.window!.rootViewController as! UINavigationController 7 | } 8 | 9 | private lazy var applicationCoordinator: Coordinator = self.makeCoordinator() 10 | 11 | func application(_ application: UIApplication, 12 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 13 | let notification = launchOptions?[.remoteNotification] as? [String: AnyObject] 14 | let deepLink = DeepLinkOption.build(with: notification) 15 | applicationCoordinator.start(with: deepLink) 16 | return true 17 | } 18 | 19 | private func makeCoordinator() -> Coordinator { 20 | return ApplicationCoordinator( 21 | router: RouterImp(rootController: self.rootController), 22 | coordinatorFactory: CoordinatorFactoryImp() 23 | ) 24 | } 25 | 26 | //MARK: Handle push notifications and deep links 27 | func application(_ application: UIApplication, 28 | didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { 29 | let dict = userInfo as? [String: AnyObject] 30 | let deepLink = DeepLinkOption.build(with: dict) 31 | applicationCoordinator.start(with: deepLink) 32 | } 33 | 34 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, 35 | restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 36 | let deepLink = DeepLinkOption.build(with: userActivity) 37 | applicationCoordinator.start(with: deepLink) 38 | return true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Base.lproj/LaunchScreen.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 | 28 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Flows/Items flow/ItemsListController.swift: -------------------------------------------------------------------------------- 1 | final class ItemsListController: UIViewController, ItemsListView { 2 | 3 | //controller handler 4 | var onItemSelect: ((ItemList) -> ())? 5 | var onCreateItem: (() -> Void)? 6 | 7 | @IBAction func addItemButtonClicked(_ sender: UIBarButtonItem) { onCreateItem?() } 8 | 9 | @IBOutlet weak var tableView: UITableView! 10 | //mock datasource 11 | var items = (0...10).map { index in return ItemList(title: "Item № \(index)", subtitle: "Item descripton") } 12 | var authCheck = false 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | title = "Items" 18 | navigationItem.rightBarButtonItem = UIBarButtonItem( 19 | barButtonSystemItem: .add, 20 | target: self, 21 | action: #selector(ItemsListController.addItemButtonClicked(_:)) 22 | ) 23 | } 24 | } 25 | 26 | extension ItemsListController: UITableViewDelegate, UITableViewDataSource { 27 | 28 | func numberOfSections(in tableView: UITableView) -> Int { 29 | return 1 30 | } 31 | 32 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 33 | return items.count 34 | } 35 | 36 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 37 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 38 | let item = items[(indexPath as NSIndexPath).row] 39 | cell.textLabel?.text = item.title 40 | cell.detailTextLabel?.text = item.subtitle 41 | return cell 42 | } 43 | 44 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 45 | onItemSelect?(items[(indexPath as NSIndexPath).row]) 46 | tableView.deselectRow(at: indexPath, animated: true) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ApplicationCoordinatorTests/Parent/BaseCoordinatorTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCoordinatorTest.swift 3 | // ApplicationCoordinator 4 | // 5 | // Created by Andrey on 04.09.16. 6 | // Copyright © 2016 Andrey Panov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ApplicationCoordinator 11 | 12 | class BaseCoordinatorTest: XCTestCase { 13 | 14 | var coordinator: BaseCoordinator! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | coordinator = BaseCoordinator() 20 | } 21 | 22 | override func tearDown() { 23 | coordinator = nil 24 | super.tearDown() 25 | } 26 | 27 | func testCoordinatorArrayInitializedOfEmptyArray() { 28 | XCTAssertTrue(coordinator.childCoordinators.isEmpty) 29 | } 30 | 31 | func testCoordinatorAddDependency() { 32 | 33 | coordinator.addDependency(coordinator) 34 | XCTAssertTrue(coordinator.childCoordinators.first is BaseCoordinator) 35 | XCTAssertTrue(coordinator.childCoordinators.count == 1) 36 | coordinator.addDependency(coordinator) 37 | XCTAssertTrue(coordinator.childCoordinators.count == 1, "Only unique reference could be added") 38 | 39 | let newCoordinator = BaseCoordinator() 40 | coordinator.addDependency(newCoordinator) 41 | XCTAssertTrue(coordinator.childCoordinators.count == 2) 42 | } 43 | 44 | func testCoordinatorRemoveDependency() { 45 | 46 | coordinator.addDependency(coordinator) 47 | XCTAssertTrue(coordinator.childCoordinators.first is BaseCoordinator) 48 | coordinator.removeDependency(coordinator) 49 | XCTAssertTrue(coordinator.childCoordinators.isEmpty) 50 | coordinator.removeDependency(coordinator) 51 | XCTAssertTrue(coordinator.childCoordinators.isEmpty, "If we try to remove removed referense, crush can't happend") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Application/ApplicationCoordinator.swift: -------------------------------------------------------------------------------- 1 | fileprivate var onboardingWasShown = false 2 | fileprivate var isAutorized = false 3 | 4 | fileprivate enum LaunchInstructor { 5 | case main, auth, onboarding 6 | 7 | static func configure( 8 | tutorialWasShown: Bool = onboardingWasShown, 9 | isAutorized: Bool = isAutorized) -> LaunchInstructor { 10 | 11 | switch (tutorialWasShown, isAutorized) { 12 | case (true, false), (false, false): return .auth 13 | case (false, true): return .onboarding 14 | case (true, true): return .main 15 | } 16 | } 17 | } 18 | 19 | final class ApplicationCoordinator: BaseCoordinator { 20 | 21 | private let coordinatorFactory: CoordinatorFactory 22 | private let router: Router 23 | 24 | private var instructor: LaunchInstructor { 25 | return LaunchInstructor.configure() 26 | } 27 | 28 | init(router: Router, coordinatorFactory: CoordinatorFactory) { 29 | self.router = router 30 | self.coordinatorFactory = coordinatorFactory 31 | } 32 | 33 | override func start(with option: DeepLinkOption?) { 34 | //start with deepLink 35 | if let option = option { 36 | switch option { 37 | case .onboarding: runOnboardingFlow() 38 | case .signUp: runAuthFlow() 39 | default: childCoordinators.forEach { coordinator in 40 | coordinator.start(with: option) 41 | } 42 | } 43 | // default start 44 | } else { 45 | switch instructor { 46 | case .onboarding: runOnboardingFlow() 47 | case .auth: runAuthFlow() 48 | case .main: runMainFlow() 49 | } 50 | } 51 | } 52 | 53 | private func runAuthFlow() { 54 | let coordinator = coordinatorFactory.makeAuthCoordinatorBox(router: router) 55 | coordinator.finishFlow = { [weak self, weak coordinator] in 56 | isAutorized = true 57 | self?.start() 58 | self?.removeDependency(coordinator) 59 | } 60 | addDependency(coordinator) 61 | coordinator.start() 62 | } 63 | 64 | private func runOnboardingFlow() { 65 | let coordinator = coordinatorFactory.makeOnboardingCoordinator(router: router) 66 | coordinator.finishFlow = { [weak self, weak coordinator] in 67 | onboardingWasShown = true 68 | self?.start() 69 | self?.removeDependency(coordinator) 70 | } 71 | addDependency(coordinator) 72 | coordinator.start() 73 | } 74 | 75 | private func runMainFlow() { 76 | let (coordinator, module) = coordinatorFactory.makeTabbarCoordinator() 77 | addDependency(coordinator) 78 | router.setRootModule(module, hideBar: true) 79 | coordinator.start() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Factories/CoordinatorFactoryImp.swift: -------------------------------------------------------------------------------- 1 | final class CoordinatorFactoryImp: CoordinatorFactory { 2 | 3 | func makeTabbarCoordinator() -> (configurator: Coordinator, toPresent: Presentable?) { 4 | let controller = TabbarController.controllerFromStoryboard(.main) 5 | let coordinator = TabbarCoordinator(tabbarView: controller, coordinatorFactory: CoordinatorFactoryImp()) 6 | return (coordinator, controller) 7 | } 8 | 9 | func makeAuthCoordinatorBox(router: Router) -> Coordinator & AuthCoordinatorOutput { 10 | 11 | let coordinator = AuthCoordinator(router: router, factory: ModuleFactoryImp()) 12 | return coordinator 13 | } 14 | 15 | func makeItemCoordinator() -> Coordinator { 16 | return makeItemCoordinator(navController: nil) 17 | } 18 | 19 | func makeOnboardingCoordinator(router: Router) -> Coordinator & OnboardingCoordinatorOutput { 20 | return OnboardingCoordinator(with: ModuleFactoryImp(), router: router) 21 | } 22 | 23 | func makeItemCoordinator(navController: UINavigationController?) -> Coordinator { 24 | let coordinator = ItemCoordinator( 25 | router: router(navController), 26 | factory: ModuleFactoryImp(), 27 | coordinatorFactory: CoordinatorFactoryImp() 28 | ) 29 | return coordinator 30 | } 31 | 32 | func makeSettingsCoordinator() -> Coordinator { 33 | return makeSettingsCoordinator(navController: nil) 34 | } 35 | 36 | func makeSettingsCoordinator(navController: UINavigationController? = nil) -> Coordinator { 37 | let coordinator = SettingsCoordinator(router: router(navController), factory: ModuleFactoryImp()) 38 | return coordinator 39 | } 40 | 41 | func makeItemCreationCoordinatorBox() -> 42 | (configurator: Coordinator & ItemCreateCoordinatorOutput, 43 | toPresent: Presentable?) { 44 | 45 | return makeItemCreationCoordinatorBox(navController: navigationController(nil)) 46 | } 47 | func makeItemCreationCoordinatorBox(navController: UINavigationController?) -> 48 | (configurator: Coordinator & ItemCreateCoordinatorOutput, 49 | toPresent: Presentable?) { 50 | 51 | let router = self.router(navController) 52 | let coordinator = ItemCreateCoordinator(router: router, factory: ModuleFactoryImp()) 53 | return (coordinator, router) 54 | } 55 | 56 | private func router(_ navController: UINavigationController?) -> Router { 57 | return RouterImp(rootController: navigationController(navController)) 58 | } 59 | 60 | private func navigationController(_ navController: UINavigationController?) -> UINavigationController { 61 | if let navController = navController { return navController } 62 | else { return UINavigationController.controllerFromStoryboard(.main) } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ApplicationCoordinatorTests/ItemsFlow/ItemCoordinatorTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemCoordinatorTest.swift 3 | // ApplicationCoordinator 4 | // 5 | // Created by Andrey on 18.09.16. 6 | // Copyright © 2016 Andrey Panov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ApplicationCoordinator 11 | 12 | class ItemCoordinatorTest: XCTestCase { 13 | 14 | private var coordinator: Coordinator! 15 | private var router: RouterMock! 16 | 17 | private var itemListOutput: ItemsListView! 18 | private var itemDetailOutput: ItemDetailView! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | router = RouterMockImp() 24 | let itemListController = ItemsListController.controllerFromStoryboard(.items) 25 | let itemDetailController = ItemDetailController.controllerFromStoryboard(.items) 26 | let factory = ItemModuleFactoryMock(itemListController: itemListController, itemDetailCntroller: itemDetailController) 27 | coordinator = ItemCoordinator(router: router, 28 | factory: factory, 29 | coordinatorFactory: CoordinatorFactoryImp()) 30 | itemListOutput = itemListController 31 | itemDetailOutput = itemDetailController 32 | 33 | } 34 | 35 | override func tearDown() { 36 | coordinator = nil 37 | router = nil 38 | itemListOutput = nil 39 | itemDetailOutput = nil 40 | 41 | super.tearDown() 42 | } 43 | 44 | func testStart() { 45 | 46 | coordinator.start() 47 | // login controller must be in navigation stack 48 | XCTAssertTrue(router.navigationStack.first is ItemsListController) 49 | XCTAssertTrue(router.navigationStack.count == 1) 50 | } 51 | 52 | func testShowItemDetail() { 53 | 54 | coordinator.start() 55 | itemListOutput.onItemSelect!(ItemList(title: "", subtitle: "")) 56 | XCTAssertTrue(router.navigationStack.last is ItemDetailController) 57 | XCTAssertTrue(router.navigationStack.count == 2) 58 | 59 | } 60 | } 61 | 62 | final class ItemModuleFactoryMock: ItemModuleFactory { 63 | 64 | private let itemListController: ItemsListController 65 | private let itemDetailCntroller: ItemDetailController 66 | 67 | init(itemListController: ItemsListController, 68 | itemDetailCntroller: ItemDetailController) { 69 | 70 | self.itemListController = itemListController 71 | self.itemDetailCntroller = itemDetailCntroller 72 | } 73 | 74 | func makeItemsOutput() -> ItemsListView { 75 | return itemListController 76 | } 77 | 78 | func makeItemDetailOutput(item: ItemList) -> ItemDetailView { 79 | return itemDetailCntroller 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ApplicationCoordinatorTests/Router/RouterTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouterTest.swift 3 | // ApplicationCoordinator 4 | // 5 | // Created by Andrey on 02.09.16. 6 | // Copyright © 2016 Andrey Panov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ApplicationCoordinator 11 | 12 | class RouterTest: XCTestCase { 13 | 14 | private var router: RouterMock! 15 | 16 | private var firstController: UIViewController! 17 | private var secondController: UIViewController! 18 | private var thirdController: UIViewController! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | router = RouterMockImp() 24 | firstController = ItemsListController.controllerFromStoryboard(.items) 25 | secondController = ItemDetailController.controllerFromStoryboard(.items) 26 | thirdController = SettingsController.controllerFromStoryboard(.settings) 27 | } 28 | 29 | override func tearDown() { 30 | 31 | router = nil 32 | firstController = nil 33 | secondController = nil 34 | thirdController = nil 35 | 36 | super.tearDown() 37 | } 38 | 39 | func testRouterSetRootModule() { 40 | 41 | router.setRootModule(firstController) 42 | XCTAssertTrue(router.navigationStack.first is ItemsListController) 43 | } 44 | 45 | func testRouterPushViewModule() { 46 | 47 | router.setRootModule(firstController) 48 | XCTAssertTrue(router.navigationStack.last is ItemsListController) 49 | router.push(secondController) 50 | XCTAssertTrue(router.navigationStack.last is ItemDetailController) 51 | } 52 | 53 | func testRouterPopViewModule() { 54 | 55 | router.setRootModule(firstController) 56 | XCTAssertTrue(router.navigationStack.last is ItemsListController) 57 | router.push(secondController) 58 | XCTAssertTrue(router.navigationStack.last is ItemDetailController) 59 | 60 | router.popModule() 61 | XCTAssertTrue(router.navigationStack.last is ItemsListController) 62 | } 63 | 64 | func testRouterPopToRootViewModule() { 65 | 66 | router.setRootModule(firstController) 67 | XCTAssertTrue(router.navigationStack.last is ItemsListController) 68 | router.push(secondController) 69 | XCTAssertTrue(router.navigationStack.last is ItemDetailController) 70 | router.push(thirdController) 71 | XCTAssertTrue(router.navigationStack.last is SettingsController) 72 | 73 | router.popToRootModule(animated: false) 74 | XCTAssertTrue(router.navigationStack.last is ItemsListController) 75 | } 76 | 77 | func testPresentViewModule() { 78 | router.present(secondController) 79 | XCTAssertTrue(router.presented is ItemDetailController) 80 | } 81 | 82 | func testDismissViewModule() { 83 | 84 | router.present(secondController) 85 | XCTAssertTrue(router.presented is ItemDetailController) 86 | router.dismissModule() 87 | XCTAssertTrue(router.presented == nil) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Presenters/RouterImp.swift: -------------------------------------------------------------------------------- 1 | final class RouterImp: NSObject, Router { 2 | 3 | private weak var rootController: UINavigationController? 4 | private var completions: [UIViewController : () -> Void] 5 | 6 | init(rootController: UINavigationController) { 7 | self.rootController = rootController 8 | completions = [:] 9 | } 10 | 11 | func toPresent() -> UIViewController? { 12 | return rootController 13 | } 14 | 15 | func present(_ module: Presentable?) { 16 | present(module, animated: true) 17 | } 18 | 19 | func present(_ module: Presentable?, animated: Bool) { 20 | guard let controller = module?.toPresent() else { return } 21 | rootController?.present(controller, animated: animated, completion: nil) 22 | } 23 | 24 | func dismissModule() { 25 | dismissModule(animated: true, completion: nil) 26 | } 27 | 28 | func dismissModule(animated: Bool, completion: (() -> Void)?) { 29 | rootController?.dismiss(animated: animated, completion: completion) 30 | } 31 | 32 | func push(_ module: Presentable?) { 33 | push(module, animated: true) 34 | } 35 | 36 | func push(_ module: Presentable?, hideBottomBar: Bool) { 37 | push(module, animated: true, hideBottomBar: hideBottomBar, completion: nil) 38 | } 39 | 40 | func push(_ module: Presentable?, animated: Bool) { 41 | push(module, animated: animated, completion: nil) 42 | } 43 | 44 | func push(_ module: Presentable?, animated: Bool, completion: (() -> Void)?) { 45 | push(module, animated: animated, hideBottomBar: false, completion: completion) 46 | } 47 | 48 | func push(_ module: Presentable?, animated: Bool, hideBottomBar: Bool, completion: (() -> Void)?) { 49 | guard 50 | let controller = module?.toPresent(), 51 | (controller is UINavigationController == false) 52 | else { assertionFailure("Deprecated push UINavigationController."); return } 53 | 54 | if let completion = completion { 55 | completions[controller] = completion 56 | } 57 | controller.hidesBottomBarWhenPushed = hideBottomBar 58 | rootController?.pushViewController(controller, animated: animated) 59 | } 60 | 61 | func popModule() { 62 | popModule(animated: true) 63 | } 64 | 65 | func popModule(animated: Bool) { 66 | if let controller = rootController?.popViewController(animated: animated) { 67 | runCompletion(for: controller) 68 | } 69 | } 70 | 71 | func setRootModule(_ module: Presentable?) { 72 | setRootModule(module, hideBar: false) 73 | } 74 | 75 | func setRootModule(_ module: Presentable?, hideBar: Bool) { 76 | guard let controller = module?.toPresent() else { return } 77 | rootController?.setViewControllers([controller], animated: false) 78 | rootController?.isNavigationBarHidden = hideBar 79 | } 80 | 81 | func popToRootModule(animated: Bool) { 82 | if let controllers = rootController?.popToRootViewController(animated: animated) { 83 | controllers.forEach { controller in 84 | runCompletion(for: controller) 85 | } 86 | } 87 | } 88 | 89 | private func runCompletion(for controller: UIViewController) { 90 | guard let completion = completions[controller] else { return } 91 | completion() 92 | completions.removeValue(forKey: controller) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ApplicationCoordinatorTests/LoginFlow/AuthCoordinatorTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthCoordinatorTest.swift 3 | // ApplicationCoordinator 4 | // 5 | // Created by Andrey on 04.09.16. 6 | // Copyright © 2016 Andrey Panov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ApplicationCoordinator 11 | 12 | class AuthCoordinatorTest: XCTestCase { 13 | 14 | private var coordinator: Coordinator! 15 | private var router: RouterMock! 16 | 17 | private var loginOutput: LoginView! 18 | private var signUpOutput: SignUpView! 19 | private var signUpController: SignUpController! 20 | private var termsOutput: TermsView! 21 | 22 | override func setUp() { 23 | super.setUp() 24 | 25 | router = RouterMockImp() 26 | let loginController = LoginController.controllerFromStoryboard(.auth) 27 | signUpController = SignUpController.controllerFromStoryboard(.auth) 28 | signUpController.view.isHidden = false 29 | let termsController = TermsController.controllerFromStoryboard(.auth) 30 | let factory = AuthModuleFactoryMock(loginController: loginController, 31 | signUpController: signUpController, 32 | termsController: termsController) 33 | coordinator = AuthCoordinator(router: router, factory: factory) 34 | 35 | loginOutput = loginController 36 | signUpOutput = signUpController 37 | termsOutput = termsController 38 | } 39 | 40 | override func tearDown() { 41 | coordinator = nil 42 | router = nil 43 | loginOutput = nil 44 | signUpOutput = nil 45 | termsOutput = nil 46 | 47 | super.tearDown() 48 | } 49 | 50 | func testStart() { 51 | 52 | coordinator.start() 53 | // showLogin() must call 54 | XCTAssertTrue(router.navigationStack.first is LoginController) 55 | XCTAssertTrue(router.navigationStack.count == 1) 56 | } 57 | 58 | func testShowSignUp() { 59 | 60 | coordinator.start() 61 | // onSignUpButtonTap event 62 | loginOutput.onSignUpButtonTap!() 63 | XCTAssertTrue(router.navigationStack.last is SignUpController) 64 | XCTAssertTrue(router.navigationStack.count == 2) 65 | } 66 | 67 | func testShowTerms() { 68 | 69 | //show login controller 70 | coordinator.start() 71 | // show signUp controller 72 | loginOutput.onSignUpButtonTap!() 73 | //show terms controller 74 | signUpOutput.onTermsButtonTap!() 75 | XCTAssertTrue(router.navigationStack.last is TermsController) 76 | XCTAssertTrue(router.navigationStack.count == 3) 77 | } 78 | } 79 | 80 | final class AuthModuleFactoryMock: AuthModuleFactory { 81 | 82 | private let loginController: LoginController 83 | private let signUpController: SignUpController 84 | private let termsController: TermsController 85 | 86 | init(loginController: LoginController, 87 | signUpController: SignUpController, 88 | termsController: TermsController) { 89 | 90 | self.loginController = loginController 91 | self.signUpController = signUpController 92 | self.termsController = termsController 93 | } 94 | 95 | func makeLoginOutput() -> LoginView { 96 | return loginController 97 | } 98 | 99 | func makeSignUpHandler() -> SignUpView { 100 | return signUpController 101 | } 102 | 103 | func makeTermsOutput() -> TermsView { 104 | return termsController 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ApplicationCoordinator 2 | A lot of developers need to change navigation flow frequently, because it depends on business tasks. And they spend a huge amount of time for re-writing code. In this approach, I demonstrate our implementation of Coordinators, the creation of a protocol-oriented, testable architecture written on pure Swift without the downcast and, also to avoid the violation of the S.O.L.I.D. principles. 3 | 4 | Based on the post about Application Coordinators [khanlou.com](http://khanlou.com/2015/10/coordinators-redux/) and Application Controller pattern description [martinfowler.com](http://martinfowler.com/eaaCatalog/applicationController.html). 5 | 6 | 7 | ### Coordinators Essential tutorial. Part I [medium.com](https://medium.com/blacklane-engineering/coordinators-essential-tutorial-part-i-376c836e9ba7) 8 | 9 | ### Coordinators Essential tutorial. Part II [medium.com](https://medium.com/@panovdev/coordinators-essential-tutorial-part-ii-b5ab3eb4a74) 10 | 11 | 12 | Example provides very basic structure with 6 controllers and 5 coordinators with mock data and logic. 13 | ![](/str.jpg) 14 | 15 | I used a protocol for coordinators in this example: 16 | ```swift 17 | protocol Coordinator: class { 18 | func start() 19 | func start(with option: DeepLinkOption?) 20 | } 21 | ``` 22 | All flow controllers have a protocols (we need to configure blocks and handle callbacks in coordinators): 23 | ```swift 24 | protocol ItemsListView: BaseView { 25 | var authNeed: (() -> ())? { get set } 26 | var onItemSelect: (ItemList -> ())? { get set } 27 | var onCreateButtonTap: (() -> ())? { get set } 28 | } 29 | ``` 30 | In this example I use factories for creating coordinators and controllers (we can mock them in tests). 31 | ```swift 32 | protocol CoordinatorFactory { 33 | func makeItemCoordinator(navController navController: UINavigationController?) -> Coordinator 34 | func makeItemCoordinator() -> Coordinator 35 | 36 | func makeItemCreationCoordinatorBox(navController: UINavigationController?) -> 37 | (configurator: Coordinator & ItemCreateCoordinatorOutput, 38 | toPresent: Presentable?) 39 | } 40 | ``` 41 | The base coordinator stores dependencies of child coordinators 42 | ```swift 43 | class BaseCoordinator: Coordinator { 44 | 45 | var childCoordinators: [Coordinator] = [] 46 | 47 | func start() { } 48 | func start(with option: DeepLinkOption?) { } 49 | 50 | // add only unique object 51 | func addDependency(_ coordinator: Coordinator) { 52 | 53 | for element in childCoordinators { 54 | if element === coordinator { return } 55 | } 56 | childCoordinators.append(coordinator) 57 | } 58 | 59 | func removeDependency(_ coordinator: Coordinator?) { 60 | guard 61 | childCoordinators.isEmpty == false, 62 | let coordinator = coordinator 63 | else { return } 64 | 65 | for (index, element) in childCoordinators.enumerated() { 66 | if element === coordinator { 67 | childCoordinators.remove(at: index) 68 | break 69 | } 70 | } 71 | } 72 | } 73 | ``` 74 | AppDelegate store lazy reference for the Application Coordinator 75 | ```swift 76 | var rootController: UINavigationController { 77 | return self.window!.rootViewController as! UINavigationController 78 | } 79 | 80 | private lazy var applicationCoordinator: Coordinator = self.makeCoordinator() 81 | 82 | func application(_ application: UIApplication, 83 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 84 | let notification = launchOptions?[.remoteNotification] as? [String: AnyObject] 85 | let deepLink = DeepLinkOption.build(with: notification) 86 | applicationCoordinator.start(with: deepLink) 87 | return true 88 | } 89 | 90 | private func makeCoordinator() -> Coordinator { 91 | return ApplicationCoordinator( 92 | router: RouterImp(rootController: self.rootController), 93 | coordinatorFactory: CoordinatorFactoryImp() 94 | ) 95 | } 96 | ``` 97 | -------------------------------------------------------------------------------- /ApplicationCoordinatorTests/Router/RouterMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouterMock.swift 3 | // ApplicationCoordinator 4 | // 5 | // Created by Andrey on 02.09.16. 6 | // Copyright © 2016 Andrey Panov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | @testable import ApplicationCoordinator 11 | 12 | protocol RouterMock: Router { 13 | var navigationStack: [UIViewController] {get} 14 | var presented: UIViewController? {get} 15 | } 16 | 17 | final class RouterMockImp: RouterMock { 18 | 19 | // in test cases router store the rootController referense 20 | private(set) var navigationStack: [UIViewController] = [] 21 | private(set) var presented: UIViewController? 22 | private var completions: [UIViewController : () -> Void] = [:] 23 | 24 | func toPresent() -> UIViewController? { 25 | return nil 26 | } 27 | 28 | //all of the actions without animation 29 | func present(_ module: Presentable?) { 30 | present(module, animated: false) 31 | } 32 | func present(_ module: Presentable?, animated: Bool) { 33 | guard let controller = module?.toPresent() else { return } 34 | presented = controller 35 | } 36 | 37 | func push(_ module: Presentable?) { 38 | push(module, animated: false) 39 | } 40 | 41 | func push(_ module: Presentable?, animated: Bool) { 42 | push(module, animated: animated, completion: nil) 43 | } 44 | 45 | func push(_ module: Presentable?, hideBottomBar: Bool) { 46 | guard 47 | let controller = module?.toPresent(), 48 | (controller is UINavigationController == false) 49 | else { assertionFailure("Deprecated push UINavigationController."); return } 50 | 51 | controller.hidesBottomBarWhenPushed = hideBottomBar 52 | 53 | push(module, animated: false) 54 | } 55 | 56 | func push(_ module: Presentable?, animated: Bool, hideBottomBar: Bool, completion: (() -> Void)?) { 57 | 58 | guard 59 | let controller = module?.toPresent(), 60 | (controller is UINavigationController == false) 61 | else { assertionFailure("Deprecated push UINavigationController."); return } 62 | 63 | controller.hidesBottomBarWhenPushed = hideBottomBar 64 | navigationStack.append(controller) 65 | } 66 | 67 | func push(_ module: Presentable?, animated: Bool, completion: (() -> Void)?) { 68 | guard 69 | let controller = module?.toPresent(), 70 | (controller is UINavigationController == false) 71 | else { assertionFailure("Deprecated push UINavigationController."); return } 72 | navigationStack.append(controller) 73 | } 74 | 75 | func popModule() { 76 | popModule(animated: false) 77 | } 78 | 79 | func popModule(animated: Bool) { 80 | let controller = navigationStack.removeLast() 81 | runCompletion(for: controller) 82 | } 83 | 84 | func dismissModule() { 85 | dismissModule(animated: false, completion: nil) 86 | } 87 | 88 | func dismissModule(animated: Bool, completion: (() -> Void)?) { 89 | presented = nil 90 | } 91 | 92 | func setRootModule(_ module: Presentable?) { 93 | guard let controller = module?.toPresent() else { return } 94 | navigationStack.append(controller) 95 | } 96 | 97 | func setRootModule(_ module: Presentable?, hideBar: Bool) { 98 | assertionFailure("This method is not used.") 99 | } 100 | 101 | func popToRootModule(animated: Bool) { 102 | guard let first = navigationStack.first else { return } 103 | 104 | navigationStack.forEach { controller in 105 | runCompletion(for: controller) 106 | } 107 | navigationStack = [first] 108 | } 109 | 110 | private func runCompletion(for controller: UIViewController) { 111 | guard let completion = completions[controller] else { return } 112 | completion() 113 | completions.removeValue(forKey: controller) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Storyboards/Create.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Base.lproj/Main.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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Storyboards/Settings.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 | 28 | 29 | 30 | 31 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Storyboards/Onboarding.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 | 30 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 76 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Storyboards/Items.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 | 28 | 29 | 30 | 31 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | «So what is a coordinator? A coordinator is an object that bosses view controllers around. Taking all of the driving logic out of your view controllers, and moving that stuff one layer up is gonna make your life a lot more awesome.» –Soroush Khanlou 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /ApplicationCoordinator/Storyboards/Auth.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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 53 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 108 | 118 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /ApplicationCoordinator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0E3CCF991CCBBB670022E830 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCF7D1CCBBB670022E830 /* AppDelegate.swift */; }; 11 | 0E3CCF9A1CCBBB670022E830 /* ApplicationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCF7E1CCBBB670022E830 /* ApplicationCoordinator.swift */; }; 12 | 0E3CCF9D1CCBBB670022E830 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E3CCF821CCBBB670022E830 /* LaunchScreen.storyboard */; }; 13 | 0E3CCF9E1CCBBB670022E830 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E3CCF841CCBBB670022E830 /* Main.storyboard */; }; 14 | 0E3CCFA01CCBBB670022E830 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCF881CCBBB670022E830 /* UIViewControllerExtension.swift */; }; 15 | 0E3CCFA81CCBBB670022E830 /* Auth.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E3CCF971CCBBB670022E830 /* Auth.storyboard */; }; 16 | 0E3CCFA91CCBBB670022E830 /* Items.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E3CCF981CCBBB670022E830 /* Items.storyboard */; }; 17 | 0E3CCFB11CCBBC760022E830 /* Create.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E3CCFAF1CCBBC760022E830 /* Create.storyboard */; }; 18 | 0E3CCFB21CCBBC760022E830 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E3CCFB01CCBBC760022E830 /* Settings.storyboard */; }; 19 | 0E3CCFB41CCBBCA20022E830 /* ItemCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFB31CCBBCA20022E830 /* ItemCoordinator.swift */; }; 20 | 0E3CCFBA1CCBBFEF0022E830 /* LoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFB91CCBBFEF0022E830 /* LoginController.swift */; }; 21 | 0E3CCFBC1CCBC0210022E830 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFBB1CCBC0210022E830 /* AuthCoordinator.swift */; }; 22 | 0E3CCFBE1CCBC04B0022E830 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFBD1CCBC04B0022E830 /* SettingsCoordinator.swift */; }; 23 | 0E3CCFC01CCBC0600022E830 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFBF1CCBC0600022E830 /* SettingsController.swift */; }; 24 | 0E3CCFC21CCBC0AA0022E830 /* ItemCreateCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFC11CCBC0AA0022E830 /* ItemCreateCoordinator.swift */; }; 25 | 0E3CCFC71CCBC6740022E830 /* ItemList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFC61CCBC6740022E830 /* ItemList.swift */; }; 26 | 0E3CCFC91CCBE87F0022E830 /* SignUpController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFC81CCBE87F0022E830 /* SignUpController.swift */; }; 27 | 0E3CCFCB1CCBFB150022E830 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CCFCA1CCBFB150022E830 /* Setting.swift */; }; 28 | 0E6A42BE1CF4DE62005746EC /* CoordinatorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42BD1CF4DE62005746EC /* CoordinatorFactory.swift */; }; 29 | 0E6A42C01CF4E06C005746EC /* ItemModuleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42BF1CF4E06C005746EC /* ItemModuleFactory.swift */; }; 30 | 0E6A42C21CF4E29F005746EC /* ItemCreateModuleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42C11CF4E29F005746EC /* ItemCreateModuleFactory.swift */; }; 31 | 0E6A42C41CF4E300005746EC /* AuthModuleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42C31CF4E300005746EC /* AuthModuleFactory.swift */; }; 32 | 0E6A42C61CF4E45F005746EC /* SettingsModuleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42C51CF4E45F005746EC /* SettingsModuleFactory.swift */; }; 33 | 0E6A42CA1CF4E81E005746EC /* ItemCreateCoordinatorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42C91CF4E81E005746EC /* ItemCreateCoordinatorOutput.swift */; }; 34 | 0E6A42CD1CF4E862005746EC /* AuthCoordinatorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42CC1CF4E862005746EC /* AuthCoordinatorOutput.swift */; }; 35 | 0E6A42D21CF627CC005746EC /* TermsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42D11CF627CC005746EC /* TermsController.swift */; }; 36 | 0E6A42D41CF62EA2005746EC /* TermsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6A42D31CF62EA2005746EC /* TermsView.swift */; }; 37 | 0EDCA2F51CEE365300A6E385 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDCA2F41CEE365300A6E385 /* Router.swift */; }; 38 | 7712177D1D7C89EA0070CDF9 /* BaseCoordinatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7712177C1D7C89EA0070CDF9 /* BaseCoordinatorTest.swift */; }; 39 | 7712177F1D7C91670070CDF9 /* SettingsCoordinatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7712177E1D7C91670070CDF9 /* SettingsCoordinatorTest.swift */; }; 40 | 771217811D7C94290070CDF9 /* AuthCoordinatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771217801D7C94290070CDF9 /* AuthCoordinatorTest.swift */; }; 41 | 77B3E7911D8E90550095527A /* ItemCoordinatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77B3E7901D8E90550095527A /* ItemCoordinatorTest.swift */; }; 42 | 77E054CB1D7937C900147DAE /* RouterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E054CA1D7937C900147DAE /* RouterTest.swift */; }; 43 | 77E054CD1D79391600147DAE /* RouterMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E054CC1D79391600147DAE /* RouterMock.swift */; }; 44 | AD1BFD591E2930C40022C865 /* DeepLinkOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1BFD581E2930C40022C865 /* DeepLinkOption.swift */; }; 45 | AD2BFC0B1E75B2730059C854 /* OnboardingModuleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2BFC0A1E75B2730059C854 /* OnboardingModuleFactory.swift */; }; 46 | AD2BFC101E75B7FE0059C854 /* OnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2BFC0D1E75B7FE0059C854 /* OnboardingController.swift */; }; 47 | AD2BFC111E75B7FE0059C854 /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2BFC0E1E75B7FE0059C854 /* PageViewController.swift */; }; 48 | AD2BFC121E75B7FE0059C854 /* StepController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2BFC0F1E75B7FE0059C854 /* StepController.swift */; }; 49 | AD2BFC141E75B81B0059C854 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2BFC131E75B81B0059C854 /* OnboardingView.swift */; }; 50 | AD2BFC161E75B95F0059C854 /* OnboardingCoordinatorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2BFC151E75B95F0059C854 /* OnboardingCoordinatorOutput.swift */; }; 51 | ADE320CE1E47941B0087146A /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE320CD1E47941B0087146A /* OnboardingCoordinator.swift */; }; 52 | ADE320D01E47943B0087146A /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ADE320CF1E47943B0087146A /* Onboarding.storyboard */; }; 53 | ADE320F71E4796D90087146A /* TabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE320F41E4796D90087146A /* TabbarController.swift */; }; 54 | ADE320F81E4796D90087146A /* TabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE320F51E4796D90087146A /* TabbarCoordinator.swift */; }; 55 | ADE320F91E4796D90087146A /* TabbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE320F61E4796D90087146A /* TabbarView.swift */; }; 56 | D5311AAF1CF8566D00777B87 /* ItemCreateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5311AAD1CF8566D00777B87 /* ItemCreateController.swift */; }; 57 | D5311AB01CF8566D00777B87 /* ItemCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5311AAE1CF8566D00777B87 /* ItemCreateView.swift */; }; 58 | D53C00BA1D1BBADA00573E71 /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53C00B91D1BBADA00573E71 /* Presentable.swift */; }; 59 | D541E7641CD9126F004C38B3 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D541E7631CD9126F004C38B3 /* Coordinator.swift */; }; 60 | D541E7661CD9128B004C38B3 /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D541E7651CD9128B004C38B3 /* BaseView.swift */; }; 61 | D541E76A1CD91304004C38B3 /* RouterImp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D541E7691CD91304004C38B3 /* RouterImp.swift */; }; 62 | D545E4A21CD1FF40002B6F8F /* BaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D545E4A11CD1FF40002B6F8F /* BaseCoordinator.swift */; }; 63 | D5505CC71CDF67F60012CBA6 /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5505CC61CDF67F60012CBA6 /* UIViewExtensions.swift */; }; 64 | D5505CC91CDF68260012CBA6 /* NSObjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5505CC81CDF68260012CBA6 /* NSObjectExtensions.swift */; }; 65 | D5505CCB1CDF68D10012CBA6 /* StoryboardsEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5505CCA1CDF68D10012CBA6 /* StoryboardsEnum.swift */; }; 66 | D5FDC84E1CEB3579001C3DF3 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FDC84D1CEB3579001C3DF3 /* LoginView.swift */; }; 67 | D5FDC8501CEB3AE0001C3DF3 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FDC84F1CEB3AE0001C3DF3 /* SignUpView.swift */; }; 68 | D5FDC8541CEB56A8001C3DF3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FDC8531CEB56A8001C3DF3 /* SettingsView.swift */; }; 69 | D5FE332A1D071ED900CE38B1 /* ModuleFactoryImp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE33261D071ED900CE38B1 /* ModuleFactoryImp.swift */; }; 70 | D5FE332F1D071F4100CE38B1 /* CoordinatorFactoryImp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE332E1D071F4100CE38B1 /* CoordinatorFactoryImp.swift */; }; 71 | D5FE33341D071F6300CE38B1 /* ItemDetailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE33301D071F6300CE38B1 /* ItemDetailController.swift */; }; 72 | D5FE33351D071F6300CE38B1 /* ItemDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE33311D071F6300CE38B1 /* ItemDetailView.swift */; }; 73 | D5FE33361D071F6300CE38B1 /* ItemsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE33321D071F6300CE38B1 /* ItemsListView.swift */; }; 74 | D5FE33371D071F6300CE38B1 /* ItemsListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FE33331D071F6300CE38B1 /* ItemsListController.swift */; }; 75 | D5FE33431D0720E500CE38B1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5FE33421D0720E500CE38B1 /* Images.xcassets */; }; 76 | /* End PBXBuildFile section */ 77 | 78 | /* Begin PBXContainerItemProxy section */ 79 | 77E054B51D79312700147DAE /* PBXContainerItemProxy */ = { 80 | isa = PBXContainerItemProxy; 81 | containerPortal = D50F9EE71C7A525E00C5A59F /* Project object */; 82 | proxyType = 1; 83 | remoteGlobalIDString = D50F9EEE1C7A525E00C5A59F; 84 | remoteInfo = ApplicationCoordinator; 85 | }; 86 | /* End PBXContainerItemProxy section */ 87 | 88 | /* Begin PBXFileReference section */ 89 | 0E3CCF7D1CCBBB670022E830 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 90 | 0E3CCF7E1CCBBB670022E830 /* ApplicationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ApplicationCoordinator.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 91 | 0E3CCF831CCBBB670022E830 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = LaunchScreen.storyboard; sourceTree = ""; }; 92 | 0E3CCF851CCBBB670022E830 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = ""; }; 93 | 0E3CCF881CCBBB670022E830 /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtension.swift; sourceTree = ""; }; 94 | 0E3CCF971CCBBB670022E830 /* Auth.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Auth.storyboard; sourceTree = ""; }; 95 | 0E3CCF981CCBBB670022E830 /* Items.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Items.storyboard; sourceTree = ""; }; 96 | 0E3CCFAF1CCBBC760022E830 /* Create.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Create.storyboard; sourceTree = ""; }; 97 | 0E3CCFB01CCBBC760022E830 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 98 | 0E3CCFB31CCBBCA20022E830 /* ItemCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ItemCoordinator.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 99 | 0E3CCFB91CCBBFEF0022E830 /* LoginController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoginController.swift; path = ../LoginController.swift; sourceTree = ""; }; 100 | 0E3CCFBB1CCBC0210022E830 /* AuthCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AuthCoordinator.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 101 | 0E3CCFBD1CCBC04B0022E830 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SettingsCoordinator.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 102 | 0E3CCFBF1CCBC0600022E830 /* SettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SettingsController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 103 | 0E3CCFC11CCBC0AA0022E830 /* ItemCreateCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ItemCreateCoordinator.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 104 | 0E3CCFC61CCBC6740022E830 /* ItemList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemList.swift; sourceTree = ""; }; 105 | 0E3CCFC81CCBE87F0022E830 /* SignUpController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SignUpController.swift; path = ../SignUpController.swift; sourceTree = ""; }; 106 | 0E3CCFCA1CCBFB150022E830 /* Setting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = ""; }; 107 | 0E6A42BD1CF4DE62005746EC /* CoordinatorFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoordinatorFactory.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 108 | 0E6A42BF1CF4E06C005746EC /* ItemModuleFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ItemModuleFactory.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 109 | 0E6A42C11CF4E29F005746EC /* ItemCreateModuleFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ItemCreateModuleFactory.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 110 | 0E6A42C31CF4E300005746EC /* AuthModuleFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AuthModuleFactory.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 111 | 0E6A42C51CF4E45F005746EC /* SettingsModuleFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SettingsModuleFactory.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 112 | 0E6A42C91CF4E81E005746EC /* ItemCreateCoordinatorOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCreateCoordinatorOutput.swift; sourceTree = ""; }; 113 | 0E6A42CC1CF4E862005746EC /* AuthCoordinatorOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthCoordinatorOutput.swift; sourceTree = ""; }; 114 | 0E6A42D11CF627CC005746EC /* TermsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermsController.swift; sourceTree = ""; }; 115 | 0E6A42D31CF62EA2005746EC /* TermsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermsView.swift; sourceTree = ""; }; 116 | 0EDCA2F41CEE365300A6E385 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; 117 | 7712177C1D7C89EA0070CDF9 /* BaseCoordinatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCoordinatorTest.swift; sourceTree = ""; }; 118 | 7712177E1D7C91670070CDF9 /* SettingsCoordinatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinatorTest.swift; sourceTree = ""; }; 119 | 771217801D7C94290070CDF9 /* AuthCoordinatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AuthCoordinatorTest.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 120 | 77ADE7671E9A724F00409A63 /* ApplicationCoordinator-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ApplicationCoordinator-Bridging-Header.h"; path = "ApplicationCoordinator/Application/ApplicationCoordinator-Bridging-Header.h"; sourceTree = SOURCE_ROOT; }; 121 | 77B3E7901D8E90550095527A /* ItemCoordinatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ItemCoordinatorTest.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 122 | 77E054B01D79312700147DAE /* ApplicationCoordinatorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ApplicationCoordinatorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 123 | 77E054B41D79312700147DAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 124 | 77E054CA1D7937C900147DAE /* RouterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterTest.swift; sourceTree = ""; }; 125 | 77E054CC1D79391600147DAE /* RouterMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterMock.swift; sourceTree = ""; }; 126 | AD1BFD581E2930C40022C865 /* DeepLinkOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeepLinkOption.swift; sourceTree = ""; }; 127 | AD2BFC0A1E75B2730059C854 /* OnboardingModuleFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingModuleFactory.swift; sourceTree = ""; }; 128 | AD2BFC0D1E75B7FE0059C854 /* OnboardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingController.swift; sourceTree = ""; }; 129 | AD2BFC0E1E75B7FE0059C854 /* PageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = ""; }; 130 | AD2BFC0F1E75B7FE0059C854 /* StepController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepController.swift; sourceTree = ""; }; 131 | AD2BFC131E75B81B0059C854 /* OnboardingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 132 | AD2BFC151E75B95F0059C854 /* OnboardingCoordinatorOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinatorOutput.swift; sourceTree = ""; }; 133 | ADE320CD1E47941B0087146A /* OnboardingCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinator.swift; sourceTree = ""; }; 134 | ADE320CF1E47943B0087146A /* Onboarding.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = ""; }; 135 | ADE320F41E4796D90087146A /* TabbarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbarController.swift; sourceTree = ""; }; 136 | ADE320F51E4796D90087146A /* TabbarCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbarCoordinator.swift; sourceTree = ""; }; 137 | ADE320F61E4796D90087146A /* TabbarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbarView.swift; sourceTree = ""; }; 138 | D50F9EEF1C7A525E00C5A59F /* ApplicationCoordinator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ApplicationCoordinator.app; sourceTree = BUILT_PRODUCTS_DIR; }; 139 | D5311AAD1CF8566D00777B87 /* ItemCreateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = ItemCreateController.swift; path = "ApplicationCoordinator/Flows/Create flow/ItemCreateController.swift"; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 140 | D5311AAE1CF8566D00777B87 /* ItemCreateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ItemCreateView.swift; path = "ApplicationCoordinator/Flows/Create flow/ItemCreateView.swift"; sourceTree = SOURCE_ROOT; }; 141 | D538B0971D0722490073895D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ApplicationCoordinator/Resources/Info.plist; sourceTree = SOURCE_ROOT; }; 142 | D53C00B91D1BBADA00573E71 /* Presentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = ""; }; 143 | D541E7631CD9126F004C38B3 /* Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; 144 | D541E7651CD9128B004C38B3 /* BaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseView.swift; sourceTree = ""; }; 145 | D541E7691CD91304004C38B3 /* RouterImp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = RouterImp.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 146 | D545E4A11CD1FF40002B6F8F /* BaseCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCoordinator.swift; sourceTree = ""; }; 147 | D5505CC61CDF67F60012CBA6 /* UIViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewExtensions.swift; sourceTree = ""; }; 148 | D5505CC81CDF68260012CBA6 /* NSObjectExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSObjectExtensions.swift; sourceTree = ""; }; 149 | D5505CCA1CDF68D10012CBA6 /* StoryboardsEnum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardsEnum.swift; sourceTree = ""; }; 150 | D5FDC84D1CEB3579001C3DF3 /* LoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoginView.swift; path = ../LoginView.swift; sourceTree = ""; }; 151 | D5FDC84F1CEB3AE0001C3DF3 /* SignUpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SignUpView.swift; path = ../SignUpView.swift; sourceTree = ""; }; 152 | D5FDC8531CEB56A8001C3DF3 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 153 | D5FE33261D071ED900CE38B1 /* ModuleFactoryImp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = ModuleFactoryImp.swift; path = ApplicationCoordinator/Factories/ModuleFactoryImp.swift; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 154 | D5FE332E1D071F4100CE38B1 /* CoordinatorFactoryImp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = CoordinatorFactoryImp.swift; path = ApplicationCoordinator/Factories/CoordinatorFactoryImp.swift; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 155 | D5FE33301D071F6300CE38B1 /* ItemDetailController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ItemDetailController.swift; path = "ApplicationCoordinator/Flows/Items flow/ItemDetailController.swift"; sourceTree = SOURCE_ROOT; }; 156 | D5FE33311D071F6300CE38B1 /* ItemDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ItemDetailView.swift; path = "ApplicationCoordinator/Flows/Items flow/ItemDetailView.swift"; sourceTree = SOURCE_ROOT; }; 157 | D5FE33321D071F6300CE38B1 /* ItemsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ItemsListView.swift; path = "ApplicationCoordinator/Flows/Items flow/ItemsListView.swift"; sourceTree = SOURCE_ROOT; }; 158 | D5FE33331D071F6300CE38B1 /* ItemsListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ItemsListController.swift; path = "ApplicationCoordinator/Flows/Items flow/ItemsListController.swift"; sourceTree = SOURCE_ROOT; }; 159 | D5FE33421D0720E500CE38B1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ApplicationCoordinator/Images.xcassets; sourceTree = SOURCE_ROOT; }; 160 | /* End PBXFileReference section */ 161 | 162 | /* Begin PBXFrameworksBuildPhase section */ 163 | 77E054AD1D79312700147DAE /* Frameworks */ = { 164 | isa = PBXFrameworksBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | D50F9EEC1C7A525E00C5A59F /* Frameworks */ = { 171 | isa = PBXFrameworksBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXFrameworksBuildPhase section */ 178 | 179 | /* Begin PBXGroup section */ 180 | 0E3CCF7C1CCBBB670022E830 /* Application */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 0E3CCF7D1CCBBB670022E830 /* AppDelegate.swift */, 184 | 0E3CCF7E1CCBBB670022E830 /* ApplicationCoordinator.swift */, 185 | ); 186 | name = Application; 187 | path = ApplicationCoordinator/Application; 188 | sourceTree = SOURCE_ROOT; 189 | }; 190 | 0E3CCF811CCBBB670022E830 /* Base.lproj */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 0E3CCF821CCBBB670022E830 /* LaunchScreen.storyboard */, 194 | ); 195 | name = Base.lproj; 196 | path = ApplicationCoordinator/Base.lproj; 197 | sourceTree = SOURCE_ROOT; 198 | }; 199 | 0E3CCF861CCBBB670022E830 /* Extensions */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | 0E3CCF881CCBBB670022E830 /* UIViewControllerExtension.swift */, 203 | D5505CC61CDF67F60012CBA6 /* UIViewExtensions.swift */, 204 | D5505CC81CDF68260012CBA6 /* NSObjectExtensions.swift */, 205 | ); 206 | name = Extensions; 207 | path = ApplicationCoordinator/Extensions; 208 | sourceTree = SOURCE_ROOT; 209 | }; 210 | 0E3CCF891CCBBB670022E830 /* Flows */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | ADE320F31E4796810087146A /* MainTabbarFlow */, 214 | ADE320CC1E4793F60087146A /* OnboardingFlow */, 215 | 0E3CCF8B1CCBBB670022E830 /* Items flow */, 216 | 0E3CCF8A1CCBBB670022E830 /* Create flow */, 217 | 0E3CCF8C1CCBBB670022E830 /* Login flow */, 218 | 0E3CCF8D1CCBBB670022E830 /* Settings flow */, 219 | ); 220 | name = Flows; 221 | path = ApplicationCoordinator/Flows; 222 | sourceTree = SOURCE_ROOT; 223 | }; 224 | 0E3CCF8A1CCBBB670022E830 /* Create flow */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | 0E6A42C91CF4E81E005746EC /* ItemCreateCoordinatorOutput.swift */, 228 | 0E3CCFC11CCBC0AA0022E830 /* ItemCreateCoordinator.swift */, 229 | 0E6A42CB1CF4E84D005746EC /* Controllers */, 230 | ); 231 | path = "Create flow"; 232 | sourceTree = ""; 233 | }; 234 | 0E3CCF8B1CCBBB670022E830 /* Items flow */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | 0E3CCFB31CCBBCA20022E830 /* ItemCoordinator.swift */, 238 | 0E6A42C81CF4E7F7005746EC /* Controllers */, 239 | ); 240 | path = "Items flow"; 241 | sourceTree = ""; 242 | }; 243 | 0E3CCF8C1CCBBB670022E830 /* Login flow */ = { 244 | isa = PBXGroup; 245 | children = ( 246 | 0E6A42CC1CF4E862005746EC /* AuthCoordinatorOutput.swift */, 247 | 0E3CCFBB1CCBC0210022E830 /* AuthCoordinator.swift */, 248 | 0E6A42CE1CF4E88C005746EC /* Controllers */, 249 | ); 250 | path = "Login flow"; 251 | sourceTree = ""; 252 | }; 253 | 0E3CCF8D1CCBBB670022E830 /* Settings flow */ = { 254 | isa = PBXGroup; 255 | children = ( 256 | 0E3CCFBD1CCBC04B0022E830 /* SettingsCoordinator.swift */, 257 | D5FDC8531CEB56A8001C3DF3 /* SettingsView.swift */, 258 | 0E3CCFBF1CCBC0600022E830 /* SettingsController.swift */, 259 | ); 260 | path = "Settings flow"; 261 | sourceTree = ""; 262 | }; 263 | 0E3CCF921CCBBB670022E830 /* Protocols */ = { 264 | isa = PBXGroup; 265 | children = ( 266 | D541E7631CD9126F004C38B3 /* Coordinator.swift */, 267 | AD1BFD581E2930C40022C865 /* DeepLinkOption.swift */, 268 | D541E7651CD9128B004C38B3 /* BaseView.swift */, 269 | D53C00B91D1BBADA00573E71 /* Presentable.swift */, 270 | ); 271 | name = Protocols; 272 | path = ApplicationCoordinator/Protocols; 273 | sourceTree = SOURCE_ROOT; 274 | }; 275 | 0E3CCF961CCBBB670022E830 /* Storyboards */ = { 276 | isa = PBXGroup; 277 | children = ( 278 | D5505CCA1CDF68D10012CBA6 /* StoryboardsEnum.swift */, 279 | 0E3CCF841CCBBB670022E830 /* Main.storyboard */, 280 | ADE320CF1E47943B0087146A /* Onboarding.storyboard */, 281 | 0E3CCF971CCBBB670022E830 /* Auth.storyboard */, 282 | 0E3CCF981CCBBB670022E830 /* Items.storyboard */, 283 | 0E3CCFAF1CCBBC760022E830 /* Create.storyboard */, 284 | 0E3CCFB01CCBBC760022E830 /* Settings.storyboard */, 285 | ); 286 | name = Storyboards; 287 | path = ApplicationCoordinator/Storyboards; 288 | sourceTree = SOURCE_ROOT; 289 | }; 290 | 0E3CCFAA1CCBBBFB0022E830 /* Resources */ = { 291 | isa = PBXGroup; 292 | children = ( 293 | 77ADE7671E9A724F00409A63 /* ApplicationCoordinator-Bridging-Header.h */, 294 | D5FE33421D0720E500CE38B1 /* Images.xcassets */, 295 | D538B0971D0722490073895D /* Info.plist */, 296 | 0E3CCF811CCBBB670022E830 /* Base.lproj */, 297 | ); 298 | name = Resources; 299 | sourceTree = ""; 300 | }; 301 | 0E3CCFC51CCBC6660022E830 /* Model */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | 0E3CCFC61CCBC6740022E830 /* ItemList.swift */, 305 | 0E3CCFCA1CCBFB150022E830 /* Setting.swift */, 306 | ); 307 | name = Model; 308 | path = ApplicationCoordinator/Model; 309 | sourceTree = SOURCE_ROOT; 310 | }; 311 | 0E6A42C71CF4E4C4005746EC /* Imp */ = { 312 | isa = PBXGroup; 313 | children = ( 314 | D5FE332E1D071F4100CE38B1 /* CoordinatorFactoryImp.swift */, 315 | D5FE33261D071ED900CE38B1 /* ModuleFactoryImp.swift */, 316 | ); 317 | name = Imp; 318 | sourceTree = ""; 319 | }; 320 | 0E6A42C81CF4E7F7005746EC /* Controllers */ = { 321 | isa = PBXGroup; 322 | children = ( 323 | D5FE33321D071F6300CE38B1 /* ItemsListView.swift */, 324 | D5FE33331D071F6300CE38B1 /* ItemsListController.swift */, 325 | D5FE33311D071F6300CE38B1 /* ItemDetailView.swift */, 326 | D5FE33301D071F6300CE38B1 /* ItemDetailController.swift */, 327 | ); 328 | name = Controllers; 329 | sourceTree = ""; 330 | }; 331 | 0E6A42CB1CF4E84D005746EC /* Controllers */ = { 332 | isa = PBXGroup; 333 | children = ( 334 | D5311AAE1CF8566D00777B87 /* ItemCreateView.swift */, 335 | D5311AAD1CF8566D00777B87 /* ItemCreateController.swift */, 336 | ); 337 | path = Controllers; 338 | sourceTree = ""; 339 | }; 340 | 0E6A42CE1CF4E88C005746EC /* Controllers */ = { 341 | isa = PBXGroup; 342 | children = ( 343 | D5FDC84D1CEB3579001C3DF3 /* LoginView.swift */, 344 | 0E3CCFB91CCBBFEF0022E830 /* LoginController.swift */, 345 | D5FDC84F1CEB3AE0001C3DF3 /* SignUpView.swift */, 346 | 0E3CCFC81CCBE87F0022E830 /* SignUpController.swift */, 347 | 0E6A42D31CF62EA2005746EC /* TermsView.swift */, 348 | 0E6A42D11CF627CC005746EC /* TermsController.swift */, 349 | ); 350 | path = Controllers; 351 | sourceTree = ""; 352 | }; 353 | 77E054B11D79312700147DAE /* ApplicationCoordinatorTests */ = { 354 | isa = PBXGroup; 355 | children = ( 356 | 77E054C91D79378400147DAE /* Factory */, 357 | 77E054C61D79377300147DAE /* Application */, 358 | 77E054C71D79377300147DAE /* Parent */, 359 | 77E054C81D79377300147DAE /* Router */, 360 | 77E054C21D79370D00147DAE /* CreateFlow */, 361 | 77E054C31D79370D00147DAE /* ItemsFlow */, 362 | 77E054C41D79370D00147DAE /* LoginFlow */, 363 | 77E054C51D79370D00147DAE /* SettingsFlow */, 364 | 77E054B41D79312700147DAE /* Info.plist */, 365 | ); 366 | path = ApplicationCoordinatorTests; 367 | sourceTree = ""; 368 | }; 369 | 77E054C21D79370D00147DAE /* CreateFlow */ = { 370 | isa = PBXGroup; 371 | children = ( 372 | ); 373 | path = CreateFlow; 374 | sourceTree = ""; 375 | }; 376 | 77E054C31D79370D00147DAE /* ItemsFlow */ = { 377 | isa = PBXGroup; 378 | children = ( 379 | 77B3E7901D8E90550095527A /* ItemCoordinatorTest.swift */, 380 | ); 381 | path = ItemsFlow; 382 | sourceTree = ""; 383 | }; 384 | 77E054C41D79370D00147DAE /* LoginFlow */ = { 385 | isa = PBXGroup; 386 | children = ( 387 | 771217801D7C94290070CDF9 /* AuthCoordinatorTest.swift */, 388 | ); 389 | path = LoginFlow; 390 | sourceTree = ""; 391 | }; 392 | 77E054C51D79370D00147DAE /* SettingsFlow */ = { 393 | isa = PBXGroup; 394 | children = ( 395 | 7712177E1D7C91670070CDF9 /* SettingsCoordinatorTest.swift */, 396 | ); 397 | path = SettingsFlow; 398 | sourceTree = ""; 399 | }; 400 | 77E054C61D79377300147DAE /* Application */ = { 401 | isa = PBXGroup; 402 | children = ( 403 | ); 404 | path = Application; 405 | sourceTree = ""; 406 | }; 407 | 77E054C71D79377300147DAE /* Parent */ = { 408 | isa = PBXGroup; 409 | children = ( 410 | 7712177C1D7C89EA0070CDF9 /* BaseCoordinatorTest.swift */, 411 | ); 412 | path = Parent; 413 | sourceTree = ""; 414 | }; 415 | 77E054C81D79377300147DAE /* Router */ = { 416 | isa = PBXGroup; 417 | children = ( 418 | 77E054CA1D7937C900147DAE /* RouterTest.swift */, 419 | 77E054CC1D79391600147DAE /* RouterMock.swift */, 420 | ); 421 | path = Router; 422 | sourceTree = ""; 423 | }; 424 | 77E054C91D79378400147DAE /* Factory */ = { 425 | isa = PBXGroup; 426 | children = ( 427 | ); 428 | path = Factory; 429 | sourceTree = ""; 430 | }; 431 | AD1BFD531E2926040022C865 /* Frameworks */ = { 432 | isa = PBXGroup; 433 | children = ( 434 | ); 435 | name = Frameworks; 436 | sourceTree = ""; 437 | }; 438 | AD2BFC0C1E75B7FE0059C854 /* Controllers */ = { 439 | isa = PBXGroup; 440 | children = ( 441 | AD2BFC131E75B81B0059C854 /* OnboardingView.swift */, 442 | AD2BFC0D1E75B7FE0059C854 /* OnboardingController.swift */, 443 | AD2BFC0E1E75B7FE0059C854 /* PageViewController.swift */, 444 | AD2BFC0F1E75B7FE0059C854 /* StepController.swift */, 445 | ); 446 | path = Controllers; 447 | sourceTree = ""; 448 | }; 449 | ADE320CC1E4793F60087146A /* OnboardingFlow */ = { 450 | isa = PBXGroup; 451 | children = ( 452 | AD2BFC151E75B95F0059C854 /* OnboardingCoordinatorOutput.swift */, 453 | ADE320CD1E47941B0087146A /* OnboardingCoordinator.swift */, 454 | AD2BFC0C1E75B7FE0059C854 /* Controllers */, 455 | ); 456 | path = OnboardingFlow; 457 | sourceTree = ""; 458 | }; 459 | ADE320F31E4796810087146A /* MainTabbarFlow */ = { 460 | isa = PBXGroup; 461 | children = ( 462 | ADE320F51E4796D90087146A /* TabbarCoordinator.swift */, 463 | ADE320F61E4796D90087146A /* TabbarView.swift */, 464 | ADE320F41E4796D90087146A /* TabbarController.swift */, 465 | ); 466 | path = MainTabbarFlow; 467 | sourceTree = ""; 468 | }; 469 | D50F9EE61C7A525E00C5A59F = { 470 | isa = PBXGroup; 471 | children = ( 472 | D50F9EF11C7A525E00C5A59F /* ApplicationCoordinator */, 473 | 77E054B11D79312700147DAE /* ApplicationCoordinatorTests */, 474 | D50F9EF01C7A525E00C5A59F /* Products */, 475 | AD1BFD531E2926040022C865 /* Frameworks */, 476 | ); 477 | sourceTree = ""; 478 | }; 479 | D50F9EF01C7A525E00C5A59F /* Products */ = { 480 | isa = PBXGroup; 481 | children = ( 482 | D50F9EEF1C7A525E00C5A59F /* ApplicationCoordinator.app */, 483 | 77E054B01D79312700147DAE /* ApplicationCoordinatorTests.xctest */, 484 | ); 485 | name = Products; 486 | sourceTree = ""; 487 | }; 488 | D50F9EF11C7A525E00C5A59F /* ApplicationCoordinator */ = { 489 | isa = PBXGroup; 490 | children = ( 491 | 0E3CCF7C1CCBBB670022E830 /* Application */, 492 | 0E3CCF921CCBBB670022E830 /* Protocols */, 493 | D541E7681CD912F3004C38B3 /* Router */, 494 | 0E3CCF861CCBBB670022E830 /* Extensions */, 495 | 0E3CCFC51CCBC6660022E830 /* Model */, 496 | D541E7671CD912BD004C38B3 /* Factories */, 497 | D545E4A01CD1FEFB002B6F8F /* Parents */, 498 | 0E3CCF891CCBBB670022E830 /* Flows */, 499 | 0E3CCF961CCBBB670022E830 /* Storyboards */, 500 | 0E3CCFAA1CCBBBFB0022E830 /* Resources */, 501 | ); 502 | name = ApplicationCoordinator; 503 | sourceTree = ""; 504 | }; 505 | D541E7671CD912BD004C38B3 /* Factories */ = { 506 | isa = PBXGroup; 507 | children = ( 508 | 0E6A42C71CF4E4C4005746EC /* Imp */, 509 | 0E6A42BD1CF4DE62005746EC /* CoordinatorFactory.swift */, 510 | 0E6A42BF1CF4E06C005746EC /* ItemModuleFactory.swift */, 511 | 0E6A42C11CF4E29F005746EC /* ItemCreateModuleFactory.swift */, 512 | 0E6A42C31CF4E300005746EC /* AuthModuleFactory.swift */, 513 | 0E6A42C51CF4E45F005746EC /* SettingsModuleFactory.swift */, 514 | AD2BFC0A1E75B2730059C854 /* OnboardingModuleFactory.swift */, 515 | ); 516 | name = Factories; 517 | path = ApplicationCoordinator/Factories; 518 | sourceTree = SOURCE_ROOT; 519 | }; 520 | D541E7681CD912F3004C38B3 /* Router */ = { 521 | isa = PBXGroup; 522 | children = ( 523 | 0EDCA2F41CEE365300A6E385 /* Router.swift */, 524 | D541E7691CD91304004C38B3 /* RouterImp.swift */, 525 | ); 526 | name = Router; 527 | path = ApplicationCoordinator/Presenters; 528 | sourceTree = SOURCE_ROOT; 529 | }; 530 | D545E4A01CD1FEFB002B6F8F /* Parents */ = { 531 | isa = PBXGroup; 532 | children = ( 533 | D545E4A11CD1FF40002B6F8F /* BaseCoordinator.swift */, 534 | ); 535 | name = Parents; 536 | path = ApplicationCoordinator/Parents; 537 | sourceTree = SOURCE_ROOT; 538 | }; 539 | /* End PBXGroup section */ 540 | 541 | /* Begin PBXNativeTarget section */ 542 | 77E054AF1D79312700147DAE /* ApplicationCoordinatorTests */ = { 543 | isa = PBXNativeTarget; 544 | buildConfigurationList = 77E054B91D79312700147DAE /* Build configuration list for PBXNativeTarget "ApplicationCoordinatorTests" */; 545 | buildPhases = ( 546 | 77E054AC1D79312700147DAE /* Sources */, 547 | 77E054AD1D79312700147DAE /* Frameworks */, 548 | 77E054AE1D79312700147DAE /* Resources */, 549 | ); 550 | buildRules = ( 551 | ); 552 | dependencies = ( 553 | 77E054B61D79312700147DAE /* PBXTargetDependency */, 554 | ); 555 | name = ApplicationCoordinatorTests; 556 | productName = ApplicationCoordinatorTests; 557 | productReference = 77E054B01D79312700147DAE /* ApplicationCoordinatorTests.xctest */; 558 | productType = "com.apple.product-type.bundle.unit-test"; 559 | }; 560 | D50F9EEE1C7A525E00C5A59F /* ApplicationCoordinator */ = { 561 | isa = PBXNativeTarget; 562 | buildConfigurationList = D50F9F011C7A525E00C5A59F /* Build configuration list for PBXNativeTarget "ApplicationCoordinator" */; 563 | buildPhases = ( 564 | D50F9EEB1C7A525E00C5A59F /* Sources */, 565 | D50F9EEC1C7A525E00C5A59F /* Frameworks */, 566 | D50F9EED1C7A525E00C5A59F /* Resources */, 567 | ); 568 | buildRules = ( 569 | ); 570 | dependencies = ( 571 | ); 572 | name = ApplicationCoordinator; 573 | productName = ApplicationCoordinatorExample; 574 | productReference = D50F9EEF1C7A525E00C5A59F /* ApplicationCoordinator.app */; 575 | productType = "com.apple.product-type.application"; 576 | }; 577 | /* End PBXNativeTarget section */ 578 | 579 | /* Begin PBXProject section */ 580 | D50F9EE71C7A525E00C5A59F /* Project object */ = { 581 | isa = PBXProject; 582 | attributes = { 583 | LastSwiftUpdateCheck = 0730; 584 | LastUpgradeCheck = 1400; 585 | ORGANIZATIONNAME = "Andrey Panov"; 586 | TargetAttributes = { 587 | 77E054AF1D79312700147DAE = { 588 | CreatedOnToolsVersion = 7.3.1; 589 | LastSwiftMigration = 1100; 590 | TestTargetID = D50F9EEE1C7A525E00C5A59F; 591 | }; 592 | D50F9EEE1C7A525E00C5A59F = { 593 | CreatedOnToolsVersion = 7.2.1; 594 | DevelopmentTeam = CMWP53JFC4; 595 | LastSwiftMigration = 1100; 596 | }; 597 | }; 598 | }; 599 | buildConfigurationList = D50F9EEA1C7A525E00C5A59F /* Build configuration list for PBXProject "ApplicationCoordinator" */; 600 | compatibilityVersion = "Xcode 3.2"; 601 | developmentRegion = en; 602 | hasScannedForEncodings = 0; 603 | knownRegions = ( 604 | en, 605 | Base, 606 | ); 607 | mainGroup = D50F9EE61C7A525E00C5A59F; 608 | productRefGroup = D50F9EF01C7A525E00C5A59F /* Products */; 609 | projectDirPath = ""; 610 | projectRoot = ""; 611 | targets = ( 612 | D50F9EEE1C7A525E00C5A59F /* ApplicationCoordinator */, 613 | 77E054AF1D79312700147DAE /* ApplicationCoordinatorTests */, 614 | ); 615 | }; 616 | /* End PBXProject section */ 617 | 618 | /* Begin PBXResourcesBuildPhase section */ 619 | 77E054AE1D79312700147DAE /* Resources */ = { 620 | isa = PBXResourcesBuildPhase; 621 | buildActionMask = 2147483647; 622 | files = ( 623 | ); 624 | runOnlyForDeploymentPostprocessing = 0; 625 | }; 626 | D50F9EED1C7A525E00C5A59F /* Resources */ = { 627 | isa = PBXResourcesBuildPhase; 628 | buildActionMask = 2147483647; 629 | files = ( 630 | 0E3CCFB11CCBBC760022E830 /* Create.storyboard in Resources */, 631 | 0E3CCFA91CCBBB670022E830 /* Items.storyboard in Resources */, 632 | D5FE33431D0720E500CE38B1 /* Images.xcassets in Resources */, 633 | 0E3CCF9E1CCBBB670022E830 /* Main.storyboard in Resources */, 634 | 0E3CCF9D1CCBBB670022E830 /* LaunchScreen.storyboard in Resources */, 635 | 0E3CCFA81CCBBB670022E830 /* Auth.storyboard in Resources */, 636 | 0E3CCFB21CCBBC760022E830 /* Settings.storyboard in Resources */, 637 | ADE320D01E47943B0087146A /* Onboarding.storyboard in Resources */, 638 | ); 639 | runOnlyForDeploymentPostprocessing = 0; 640 | }; 641 | /* End PBXResourcesBuildPhase section */ 642 | 643 | /* Begin PBXSourcesBuildPhase section */ 644 | 77E054AC1D79312700147DAE /* Sources */ = { 645 | isa = PBXSourcesBuildPhase; 646 | buildActionMask = 2147483647; 647 | files = ( 648 | 77E054CD1D79391600147DAE /* RouterMock.swift in Sources */, 649 | 77B3E7911D8E90550095527A /* ItemCoordinatorTest.swift in Sources */, 650 | 7712177F1D7C91670070CDF9 /* SettingsCoordinatorTest.swift in Sources */, 651 | 77E054CB1D7937C900147DAE /* RouterTest.swift in Sources */, 652 | 7712177D1D7C89EA0070CDF9 /* BaseCoordinatorTest.swift in Sources */, 653 | 771217811D7C94290070CDF9 /* AuthCoordinatorTest.swift in Sources */, 654 | ); 655 | runOnlyForDeploymentPostprocessing = 0; 656 | }; 657 | D50F9EEB1C7A525E00C5A59F /* Sources */ = { 658 | isa = PBXSourcesBuildPhase; 659 | buildActionMask = 2147483647; 660 | files = ( 661 | 0E3CCF991CCBBB670022E830 /* AppDelegate.swift in Sources */, 662 | 0E6A42C01CF4E06C005746EC /* ItemModuleFactory.swift in Sources */, 663 | 0E3CCFBA1CCBBFEF0022E830 /* LoginController.swift in Sources */, 664 | 0E3CCFC71CCBC6740022E830 /* ItemList.swift in Sources */, 665 | ADE320F81E4796D90087146A /* TabbarCoordinator.swift in Sources */, 666 | 0E6A42C41CF4E300005746EC /* AuthModuleFactory.swift in Sources */, 667 | D541E7661CD9128B004C38B3 /* BaseView.swift in Sources */, 668 | 0E6A42C21CF4E29F005746EC /* ItemCreateModuleFactory.swift in Sources */, 669 | D53C00BA1D1BBADA00573E71 /* Presentable.swift in Sources */, 670 | 0E6A42D21CF627CC005746EC /* TermsController.swift in Sources */, 671 | 0EDCA2F51CEE365300A6E385 /* Router.swift in Sources */, 672 | 0E3CCFB41CCBBCA20022E830 /* ItemCoordinator.swift in Sources */, 673 | D5505CCB1CDF68D10012CBA6 /* StoryboardsEnum.swift in Sources */, 674 | AD2BFC161E75B95F0059C854 /* OnboardingCoordinatorOutput.swift in Sources */, 675 | D5311AAF1CF8566D00777B87 /* ItemCreateController.swift in Sources */, 676 | 0E3CCFBC1CCBC0210022E830 /* AuthCoordinator.swift in Sources */, 677 | AD2BFC121E75B7FE0059C854 /* StepController.swift in Sources */, 678 | D5FE33371D071F6300CE38B1 /* ItemsListController.swift in Sources */, 679 | 0E3CCFC01CCBC0600022E830 /* SettingsController.swift in Sources */, 680 | 0E6A42BE1CF4DE62005746EC /* CoordinatorFactory.swift in Sources */, 681 | D5505CC91CDF68260012CBA6 /* NSObjectExtensions.swift in Sources */, 682 | ADE320CE1E47941B0087146A /* OnboardingCoordinator.swift in Sources */, 683 | AD2BFC141E75B81B0059C854 /* OnboardingView.swift in Sources */, 684 | 0E6A42CA1CF4E81E005746EC /* ItemCreateCoordinatorOutput.swift in Sources */, 685 | D541E76A1CD91304004C38B3 /* RouterImp.swift in Sources */, 686 | D5FDC8501CEB3AE0001C3DF3 /* SignUpView.swift in Sources */, 687 | AD2BFC111E75B7FE0059C854 /* PageViewController.swift in Sources */, 688 | 0E6A42D41CF62EA2005746EC /* TermsView.swift in Sources */, 689 | D5FDC84E1CEB3579001C3DF3 /* LoginView.swift in Sources */, 690 | 0E3CCFC91CCBE87F0022E830 /* SignUpController.swift in Sources */, 691 | D5FE33361D071F6300CE38B1 /* ItemsListView.swift in Sources */, 692 | D5311AB01CF8566D00777B87 /* ItemCreateView.swift in Sources */, 693 | AD2BFC0B1E75B2730059C854 /* OnboardingModuleFactory.swift in Sources */, 694 | AD2BFC101E75B7FE0059C854 /* OnboardingController.swift in Sources */, 695 | D5FE332A1D071ED900CE38B1 /* ModuleFactoryImp.swift in Sources */, 696 | D5505CC71CDF67F60012CBA6 /* UIViewExtensions.swift in Sources */, 697 | AD1BFD591E2930C40022C865 /* DeepLinkOption.swift in Sources */, 698 | D5FE332F1D071F4100CE38B1 /* CoordinatorFactoryImp.swift in Sources */, 699 | 0E6A42C61CF4E45F005746EC /* SettingsModuleFactory.swift in Sources */, 700 | ADE320F91E4796D90087146A /* TabbarView.swift in Sources */, 701 | 0E3CCFA01CCBBB670022E830 /* UIViewControllerExtension.swift in Sources */, 702 | 0E3CCFC21CCBC0AA0022E830 /* ItemCreateCoordinator.swift in Sources */, 703 | D5FE33351D071F6300CE38B1 /* ItemDetailView.swift in Sources */, 704 | 0E3CCF9A1CCBBB670022E830 /* ApplicationCoordinator.swift in Sources */, 705 | D5FE33341D071F6300CE38B1 /* ItemDetailController.swift in Sources */, 706 | D5FDC8541CEB56A8001C3DF3 /* SettingsView.swift in Sources */, 707 | 0E6A42CD1CF4E862005746EC /* AuthCoordinatorOutput.swift in Sources */, 708 | D545E4A21CD1FF40002B6F8F /* BaseCoordinator.swift in Sources */, 709 | 0E3CCFBE1CCBC04B0022E830 /* SettingsCoordinator.swift in Sources */, 710 | 0E3CCFCB1CCBFB150022E830 /* Setting.swift in Sources */, 711 | D541E7641CD9126F004C38B3 /* Coordinator.swift in Sources */, 712 | ADE320F71E4796D90087146A /* TabbarController.swift in Sources */, 713 | ); 714 | runOnlyForDeploymentPostprocessing = 0; 715 | }; 716 | /* End PBXSourcesBuildPhase section */ 717 | 718 | /* Begin PBXTargetDependency section */ 719 | 77E054B61D79312700147DAE /* PBXTargetDependency */ = { 720 | isa = PBXTargetDependency; 721 | target = D50F9EEE1C7A525E00C5A59F /* ApplicationCoordinator */; 722 | targetProxy = 77E054B51D79312700147DAE /* PBXContainerItemProxy */; 723 | }; 724 | /* End PBXTargetDependency section */ 725 | 726 | /* Begin PBXVariantGroup section */ 727 | 0E3CCF821CCBBB670022E830 /* LaunchScreen.storyboard */ = { 728 | isa = PBXVariantGroup; 729 | children = ( 730 | 0E3CCF831CCBBB670022E830 /* Base */, 731 | ); 732 | name = LaunchScreen.storyboard; 733 | sourceTree = ""; 734 | }; 735 | 0E3CCF841CCBBB670022E830 /* Main.storyboard */ = { 736 | isa = PBXVariantGroup; 737 | children = ( 738 | 0E3CCF851CCBBB670022E830 /* Base */, 739 | ); 740 | name = Main.storyboard; 741 | path = ../Base.lproj; 742 | sourceTree = ""; 743 | }; 744 | /* End PBXVariantGroup section */ 745 | 746 | /* Begin XCBuildConfiguration section */ 747 | 77E054B71D79312700147DAE /* Debug */ = { 748 | isa = XCBuildConfiguration; 749 | buildSettings = { 750 | BUNDLE_LOADER = "$(TEST_HOST)"; 751 | CLANG_ANALYZER_NONNULL = YES; 752 | CLANG_ENABLE_MODULES = YES; 753 | INFOPLIST_FILE = ApplicationCoordinatorTests/Info.plist; 754 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 755 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 756 | PRODUCT_BUNDLE_IDENTIFIER = com.panovdev.ApplicationCoordinatorTests; 757 | PRODUCT_NAME = "$(TARGET_NAME)"; 758 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 759 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 760 | SWIFT_VERSION = 5.0; 761 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ApplicationCoordinator.app/ApplicationCoordinator"; 762 | }; 763 | name = Debug; 764 | }; 765 | 77E054B81D79312700147DAE /* Release */ = { 766 | isa = XCBuildConfiguration; 767 | buildSettings = { 768 | BUNDLE_LOADER = "$(TEST_HOST)"; 769 | CLANG_ANALYZER_NONNULL = YES; 770 | CLANG_ENABLE_MODULES = YES; 771 | INFOPLIST_FILE = ApplicationCoordinatorTests/Info.plist; 772 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 773 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 774 | PRODUCT_BUNDLE_IDENTIFIER = com.panovdev.ApplicationCoordinatorTests; 775 | PRODUCT_NAME = "$(TARGET_NAME)"; 776 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 777 | SWIFT_VERSION = 5.0; 778 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ApplicationCoordinator.app/ApplicationCoordinator"; 779 | }; 780 | name = Release; 781 | }; 782 | D50F9EFF1C7A525E00C5A59F /* Debug */ = { 783 | isa = XCBuildConfiguration; 784 | buildSettings = { 785 | ALWAYS_SEARCH_USER_PATHS = NO; 786 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 787 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 788 | CLANG_CXX_LIBRARY = "libc++"; 789 | CLANG_ENABLE_MODULES = YES; 790 | CLANG_ENABLE_OBJC_ARC = YES; 791 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 792 | CLANG_WARN_BOOL_CONVERSION = YES; 793 | CLANG_WARN_COMMA = YES; 794 | CLANG_WARN_CONSTANT_CONVERSION = YES; 795 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 796 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 797 | CLANG_WARN_EMPTY_BODY = YES; 798 | CLANG_WARN_ENUM_CONVERSION = YES; 799 | CLANG_WARN_INFINITE_RECURSION = YES; 800 | CLANG_WARN_INT_CONVERSION = YES; 801 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 802 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 803 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 804 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 805 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 806 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 807 | CLANG_WARN_STRICT_PROTOTYPES = YES; 808 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 809 | CLANG_WARN_UNREACHABLE_CODE = YES; 810 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 811 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 812 | COPY_PHASE_STRIP = NO; 813 | DEBUG_INFORMATION_FORMAT = dwarf; 814 | ENABLE_STRICT_OBJC_MSGSEND = YES; 815 | ENABLE_TESTABILITY = YES; 816 | GCC_C_LANGUAGE_STANDARD = gnu99; 817 | GCC_DYNAMIC_NO_PIC = NO; 818 | GCC_NO_COMMON_BLOCKS = YES; 819 | GCC_OPTIMIZATION_LEVEL = 0; 820 | GCC_PREPROCESSOR_DEFINITIONS = ( 821 | "DEBUG=1", 822 | "$(inherited)", 823 | ); 824 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 825 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 826 | GCC_WARN_UNDECLARED_SELECTOR = YES; 827 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 828 | GCC_WARN_UNUSED_FUNCTION = YES; 829 | GCC_WARN_UNUSED_VARIABLE = YES; 830 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 831 | MTL_ENABLE_DEBUG_INFO = YES; 832 | ONLY_ACTIVE_ARCH = YES; 833 | SDKROOT = iphoneos; 834 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 835 | }; 836 | name = Debug; 837 | }; 838 | D50F9F001C7A525E00C5A59F /* Release */ = { 839 | isa = XCBuildConfiguration; 840 | buildSettings = { 841 | ALWAYS_SEARCH_USER_PATHS = NO; 842 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 843 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 844 | CLANG_CXX_LIBRARY = "libc++"; 845 | CLANG_ENABLE_MODULES = YES; 846 | CLANG_ENABLE_OBJC_ARC = YES; 847 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 848 | CLANG_WARN_BOOL_CONVERSION = YES; 849 | CLANG_WARN_COMMA = YES; 850 | CLANG_WARN_CONSTANT_CONVERSION = YES; 851 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 852 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 853 | CLANG_WARN_EMPTY_BODY = YES; 854 | CLANG_WARN_ENUM_CONVERSION = YES; 855 | CLANG_WARN_INFINITE_RECURSION = YES; 856 | CLANG_WARN_INT_CONVERSION = YES; 857 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 858 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 859 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 860 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 861 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 862 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 863 | CLANG_WARN_STRICT_PROTOTYPES = YES; 864 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 865 | CLANG_WARN_UNREACHABLE_CODE = YES; 866 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 867 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 868 | COPY_PHASE_STRIP = NO; 869 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 870 | ENABLE_NS_ASSERTIONS = NO; 871 | ENABLE_STRICT_OBJC_MSGSEND = YES; 872 | GCC_C_LANGUAGE_STANDARD = gnu99; 873 | GCC_NO_COMMON_BLOCKS = YES; 874 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 875 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 876 | GCC_WARN_UNDECLARED_SELECTOR = YES; 877 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 878 | GCC_WARN_UNUSED_FUNCTION = YES; 879 | GCC_WARN_UNUSED_VARIABLE = YES; 880 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 881 | MTL_ENABLE_DEBUG_INFO = NO; 882 | SDKROOT = iphoneos; 883 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 884 | VALIDATE_PRODUCT = YES; 885 | }; 886 | name = Release; 887 | }; 888 | D50F9F021C7A525E00C5A59F /* Debug */ = { 889 | isa = XCBuildConfiguration; 890 | buildSettings = { 891 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 892 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Brand Assets"; 893 | CLANG_ENABLE_MODULES = YES; 894 | DEVELOPMENT_TEAM = CMWP53JFC4; 895 | INFOPLIST_FILE = "$(SRCROOT)/ApplicationCoordinator/Resources/Info.plist"; 896 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 897 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 898 | PRODUCT_BUNDLE_IDENTIFIER = com.panovdev.ApplicationCoordinator; 899 | PRODUCT_NAME = ApplicationCoordinator; 900 | SWIFT_OBJC_BRIDGING_HEADER = "ApplicationCoordinator/Application/ApplicationCoordinator-Bridging-Header.h"; 901 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 902 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 903 | SWIFT_VERSION = 5.0; 904 | }; 905 | name = Debug; 906 | }; 907 | D50F9F031C7A525E00C5A59F /* Release */ = { 908 | isa = XCBuildConfiguration; 909 | buildSettings = { 910 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 911 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Brand Assets"; 912 | CLANG_ENABLE_MODULES = YES; 913 | DEVELOPMENT_TEAM = CMWP53JFC4; 914 | INFOPLIST_FILE = "$(SRCROOT)/ApplicationCoordinator/Resources/Info.plist"; 915 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 916 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 917 | PRODUCT_BUNDLE_IDENTIFIER = com.panovdev.ApplicationCoordinator; 918 | PRODUCT_NAME = ApplicationCoordinator; 919 | SWIFT_OBJC_BRIDGING_HEADER = "ApplicationCoordinator/Application/ApplicationCoordinator-Bridging-Header.h"; 920 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 921 | SWIFT_VERSION = 5.0; 922 | }; 923 | name = Release; 924 | }; 925 | /* End XCBuildConfiguration section */ 926 | 927 | /* Begin XCConfigurationList section */ 928 | 77E054B91D79312700147DAE /* Build configuration list for PBXNativeTarget "ApplicationCoordinatorTests" */ = { 929 | isa = XCConfigurationList; 930 | buildConfigurations = ( 931 | 77E054B71D79312700147DAE /* Debug */, 932 | 77E054B81D79312700147DAE /* Release */, 933 | ); 934 | defaultConfigurationIsVisible = 0; 935 | defaultConfigurationName = Release; 936 | }; 937 | D50F9EEA1C7A525E00C5A59F /* Build configuration list for PBXProject "ApplicationCoordinator" */ = { 938 | isa = XCConfigurationList; 939 | buildConfigurations = ( 940 | D50F9EFF1C7A525E00C5A59F /* Debug */, 941 | D50F9F001C7A525E00C5A59F /* Release */, 942 | ); 943 | defaultConfigurationIsVisible = 0; 944 | defaultConfigurationName = Release; 945 | }; 946 | D50F9F011C7A525E00C5A59F /* Build configuration list for PBXNativeTarget "ApplicationCoordinator" */ = { 947 | isa = XCConfigurationList; 948 | buildConfigurations = ( 949 | D50F9F021C7A525E00C5A59F /* Debug */, 950 | D50F9F031C7A525E00C5A59F /* Release */, 951 | ); 952 | defaultConfigurationIsVisible = 0; 953 | defaultConfigurationName = Release; 954 | }; 955 | /* End XCConfigurationList section */ 956 | }; 957 | rootObject = D50F9EE71C7A525E00C5A59F /* Project object */; 958 | } 959 | --------------------------------------------------------------------------------