├── .gitignore ├── AddToWishlistIntent ├── AddToWishlistIntentHandler.swift ├── Info.plist ├── IntentHandler.swift └── Intents.intentdefinition ├── AddToWishlistIntentUI ├── Base.lproj │ └── MainInterface.storyboard ├── Info.plist └── IntentViewController.swift ├── Assets ├── app.gif ├── deeplink.gif └── flow.png ├── LICENSE ├── README.md ├── RoutingExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── RoutingExample.xcscheme ├── RoutingExample ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── first.imageset │ │ ├── Contents.json │ │ └── first.pdf │ └── second.imageset │ │ ├── Contents.json │ │ └── second.pdf ├── Authentication │ ├── ForgottenPasswordViewController.swift │ ├── ForgottenPasswordViewModel.swift │ ├── LoginViewController.swift │ ├── LoginViewModel.swift │ ├── SignUpViewController.swift │ └── SignUpViewModel.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── PopUps │ ├── PopUpViewController.swift │ └── PopUpViewModel.swift ├── Product │ ├── ProductViewController.swift │ └── ProductViewModel.swift ├── Routing │ ├── Animator │ │ ├── AnimatedTransitioning.swift │ │ └── FadeAnimatedTransitioning.swift │ ├── Router.swift │ ├── Routers │ │ ├── DeeplinkRouter.swift │ │ ├── DefaultRouter.swift │ │ └── SiriRouter.swift │ ├── Routes │ │ ├── ForgottenPasswordRoute.swift │ │ ├── LoginRoute.swift │ │ ├── PopUpRoute.swift │ │ ├── ProductRoute.swift │ │ ├── SignUpRoute.swift │ │ ├── SiriRoute.swift │ │ └── Tabs │ │ │ ├── ShopTabRoute.swift │ │ │ └── WishlistTabRoute.swift │ └── Transitions │ │ ├── AnimatedTransition.swift │ │ ├── EmptyTransition.swift │ │ ├── ModalTransition.swift │ │ ├── PushTransition.swift │ │ └── Transition.swift ├── SceneDelegate.swift ├── Tabs │ ├── MainTabBarController.swift │ ├── ShopViewController.swift │ ├── ShopViewModel.swift │ ├── WishlistViewController.swift │ └── WishlistViewModel.swift └── Utils │ ├── DefaultButton.swift │ ├── LayoutHelper.swift │ └── UIViewController.swift └── RoutingExampleTests ├── Info.plist └── RoutingExampleTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 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 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | .DS_Store -------------------------------------------------------------------------------- /AddToWishlistIntent/AddToWishlistIntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddToWishlistIntentHandler.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Intents 11 | 12 | class AddToWishlistIntentHandler : NSObject, AddToWishlistIntentHandling { 13 | func handle(intent: AddToWishlistIntent, completion: @escaping (AddToWishlistIntentResponse) -> Void) { 14 | let response = AddToWishlistIntentResponse(code: .success, userActivity: NSUserActivity(activityType: "addToWishlist")) 15 | completion(response) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AddToWishlistIntent/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | AddToWishlistIntent 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | IntentsRestrictedWhileLocked 28 | 29 | IntentsRestrictedWhileProtectedDataUnavailable 30 | 31 | IntentsSupported 32 | 33 | AddToWishlistIntent 34 | 35 | 36 | NSExtensionPointIdentifier 37 | com.apple.intents-service 38 | NSExtensionPrincipalClass 39 | $(PRODUCT_MODULE_NAME).IntentHandler 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /AddToWishlistIntent/IntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentHandler.swift 3 | // AddToWishlistIntent 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Intents 10 | 11 | class IntentHandler: INExtension { 12 | override func handler(for intent: INIntent) -> Any { 13 | guard intent is AddToWishlistIntent else { fatalError("Unhandled intent type: \(intent)") } 14 | return AddToWishlistIntentHandler() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AddToWishlistIntent/Intents.intentdefinition: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INEnums 6 | 7 | INIntentDefinitionModelVersion 8 | 1.1 9 | INIntentDefinitionNamespace 10 | 4HfncZ 11 | INIntentDefinitionSystemVersion 12 | 18G3020 13 | INIntentDefinitionToolsBuildVersion 14 | 11C504 15 | INIntentDefinitionToolsVersion 16 | 11.3.1 17 | INIntents 18 | 19 | 20 | INIntentCategory 21 | create 22 | INIntentDescription 23 | Add a product to Wishlist 24 | INIntentDescriptionID 25 | WqWD1V 26 | INIntentLastParameterTag 27 | 1 28 | INIntentName 29 | AddToWishlist 30 | INIntentParameterCombinations 31 | 32 | name 33 | 34 | INIntentParameterCombinationSupportsBackgroundExecution 35 | 36 | INIntentParameterCombinationTitle 37 | Add to wishlist ${name} 38 | INIntentParameterCombinationTitleID 39 | oLZ9ch 40 | 41 | 42 | INIntentParameters 43 | 44 | 45 | INIntentParameterDisplayName 46 | Name 47 | INIntentParameterDisplayNameID 48 | lpEyWX 49 | INIntentParameterDisplayPriority 50 | 1 51 | INIntentParameterMetadata 52 | 53 | INIntentParameterMetadataCapitalization 54 | Sentences 55 | 56 | INIntentParameterName 57 | name 58 | INIntentParameterTag 59 | 1 60 | INIntentParameterType 61 | String 62 | 63 | 64 | INIntentResponse 65 | 66 | INIntentResponseCodes 67 | 68 | 69 | INIntentResponseCodeFormatString 70 | ${name} has been added to wishlist. 71 | INIntentResponseCodeFormatStringID 72 | YJJAUK 73 | INIntentResponseCodeName 74 | success 75 | INIntentResponseCodeSuccess 76 | 77 | 78 | 79 | INIntentResponseCodeFormatString 80 | Couldn't add ${name} to wishlist. 81 | INIntentResponseCodeFormatStringID 82 | cSiHvf 83 | INIntentResponseCodeName 84 | failure 85 | 86 | 87 | INIntentResponseLastParameterTag 88 | 1 89 | INIntentResponseParameters 90 | 91 | 92 | INIntentResponseParameterDisplayName 93 | Name 94 | INIntentResponseParameterDisplayNameID 95 | V7z37G 96 | INIntentResponseParameterDisplayPriority 97 | 1 98 | INIntentResponseParameterName 99 | name 100 | INIntentResponseParameterTag 101 | 1 102 | INIntentResponseParameterType 103 | String 104 | 105 | 106 | 107 | INIntentTitle 108 | Add To Wishlist 109 | INIntentTitleID 110 | OfT2dJ 111 | INIntentType 112 | Custom 113 | INIntentVerb 114 | Add 115 | 116 | 117 | INTypes 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /AddToWishlistIntentUI/Base.lproj/MainInterface.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 | -------------------------------------------------------------------------------- /AddToWishlistIntentUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | AddToWishlistIntentUI 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | IntentsSupported 28 | 29 | AddToWishlistIntent 30 | INSendMessageIntent 31 | 32 | 33 | NSExtensionMainStoryboard 34 | MainInterface 35 | NSExtensionPointIdentifier 36 | com.apple.intents-ui-service 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /AddToWishlistIntentUI/IntentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentViewController.swift 3 | // AddToWishlistIntentUI 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import IntentsUI 10 | 11 | // As an example, this extension's Info.plist has been configured to handle interactions for INSendMessageIntent. 12 | // You will want to replace this or add other intents as appropriate. 13 | // The intents whose interactions you wish to handle must be declared in the extension's Info.plist. 14 | 15 | // You can test this example integration by saying things to Siri like: 16 | // "Send a message using " 17 | 18 | class IntentViewController: UIViewController, INUIHostedViewControlling { 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | // Do any additional setup after loading the view. 23 | } 24 | 25 | // MARK: - INUIHostedViewControlling 26 | 27 | // Prepare your view controller for the interaction to handle. 28 | func configureView(for parameters: Set, of interaction: INInteraction, interactiveBehavior: INUIInteractiveBehavior, context: INUIHostedViewContext, completion: @escaping (Bool, Set, CGSize) -> Void) { 29 | // Do configuration here, including preparing views and calculating a desired size for presentation. 30 | completion(true, parameters, self.desiredSize) 31 | } 32 | 33 | var desiredSize: CGSize { 34 | return self.extensionContext!.hostedViewMaximumAllowedSize 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Assets/app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CassiusPacheco/iOS-Routing-Example/54f2e214efb00f058dc91863db38ad9591bb1cdc/Assets/app.gif -------------------------------------------------------------------------------- /Assets/deeplink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CassiusPacheco/iOS-Routing-Example/54f2e214efb00f058dc91863db38ad9591bb1cdc/Assets/deeplink.gif -------------------------------------------------------------------------------- /Assets/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CassiusPacheco/iOS-Routing-Example/54f2e214efb00f058dc91863db38ad9591bb1cdc/Assets/flow.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cassius Pacheco 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean, Simple and Composable Routing for iOS Apps 2 | 3 | This is the [first part](https://cassiuspacheco.com/clean-simple-and-composable-routing-for-ios-apps-ck7qv6kgo0063zns1gdpgwrue) of a series of blog posts about [Clean, Simple and Composable Routing for iOS Apps](https://hashnode.com/series/clean-simple-and-composable-routing-for-ios-apps-ck7vm42k401n4zis1wu4ar2od). 4 | 5 | ## App's Flow Diagram 6 | 7 | ![flow](./Assets/flow.png) 8 | 9 | ## App's Flow Gif 10 | 11 | ![flow](./Assets/app.gif) 12 | 13 | ## Deeplinking Example 14 | 15 | ![flow](./Assets/deeplink.gif) 16 | -------------------------------------------------------------------------------- /RoutingExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6A10678A241CDA3B00844681 /* WishlistTabRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A106788241CDA3B00844681 /* WishlistTabRoute.swift */; }; 11 | 6A10678B241CDA3B00844681 /* ShopTabRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A106789241CDA3B00844681 /* ShopTabRoute.swift */; }; 12 | 6A61168224136AE30099C25F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A61168124136AE30099C25F /* AppDelegate.swift */; }; 13 | 6A61168424136AE30099C25F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A61168324136AE30099C25F /* SceneDelegate.swift */; }; 14 | 6A61168624136AE30099C25F /* ShopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A61168524136AE30099C25F /* ShopViewController.swift */; }; 15 | 6A61168D24136AE50099C25F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A61168C24136AE50099C25F /* Assets.xcassets */; }; 16 | 6A61169024136AE50099C25F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A61168E24136AE50099C25F /* LaunchScreen.storyboard */; }; 17 | 6A61169B24136AE50099C25F /* RoutingExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A61169A24136AE50099C25F /* RoutingExampleTests.swift */; }; 18 | 6A6116A624136C5C0099C25F /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116A524136C5C0099C25F /* MainTabBarController.swift */; }; 19 | 6A6116A824136EF30099C25F /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116A724136EF30099C25F /* LoginViewController.swift */; }; 20 | 6A6116AA24136EFE0099C25F /* ProductViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116A924136EFE0099C25F /* ProductViewController.swift */; }; 21 | 6A6116AC24136F1B0099C25F /* ForgottenPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116AB24136F1B0099C25F /* ForgottenPasswordViewController.swift */; }; 22 | 6A6116AE24136F3B0099C25F /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116AD24136F3B0099C25F /* SignUpViewController.swift */; }; 23 | 6A6116B024136F470099C25F /* PopUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116AF24136F470099C25F /* PopUpViewController.swift */; }; 24 | 6A6116B924136FFD0099C25F /* WishlistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116B824136FFD0099C25F /* WishlistViewController.swift */; }; 25 | 6A6116BD241372270099C25F /* LayoutHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116BC241372270099C25F /* LayoutHelper.swift */; }; 26 | 6A6116BF241372C70099C25F /* DefaultButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6116BE241372C70099C25F /* DefaultButton.swift */; }; 27 | 6AD8D56A2413848700889F2D /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5612413848700889F2D /* Router.swift */; }; 28 | 6AD8D56B2413848700889F2D /* ModalTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5632413848700889F2D /* ModalTransition.swift */; }; 29 | 6AD8D56C2413848700889F2D /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5642413848700889F2D /* Transition.swift */; }; 30 | 6AD8D56D2413848700889F2D /* PushTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5652413848700889F2D /* PushTransition.swift */; }; 31 | 6AD8D5722413853B00889F2D /* SignUpRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5702413853A00889F2D /* SignUpRoute.swift */; }; 32 | 6AD8D5732413853B00889F2D /* LoginRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5712413853B00889F2D /* LoginRoute.swift */; }; 33 | 6AD8D5752413B16300889F2D /* ForgottenPasswordRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5742413B16300889F2D /* ForgottenPasswordRoute.swift */; }; 34 | 6AD8D5772413B24000889F2D /* PopUpRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5762413B24000889F2D /* PopUpRoute.swift */; }; 35 | 6AD8D5792413B81C00889F2D /* ProductRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5782413B81C00889F2D /* ProductRoute.swift */; }; 36 | 6AD8D57C2413D33D00889F2D /* DefaultRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D57B2413D33D00889F2D /* DefaultRouter.swift */; }; 37 | 6AD8D57E2413D55300889F2D /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D57D2413D55300889F2D /* LoginViewModel.swift */; }; 38 | 6AD8D5802413D55F00889F2D /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D57F2413D55F00889F2D /* SignUpViewModel.swift */; }; 39 | 6AD8D5822413D56C00889F2D /* ForgottenPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5812413D56C00889F2D /* ForgottenPasswordViewModel.swift */; }; 40 | 6AD8D5842413D72400889F2D /* ShopViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5832413D72400889F2D /* ShopViewModel.swift */; }; 41 | 6AD8D5862413D73B00889F2D /* WishlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5852413D73B00889F2D /* WishlistViewModel.swift */; }; 42 | 6AD8D5882413D83900889F2D /* PopUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5872413D83900889F2D /* PopUpViewModel.swift */; }; 43 | 6AD8D58A2413D8C800889F2D /* ProductViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5892413D8C800889F2D /* ProductViewModel.swift */; }; 44 | 6AD8D58C2413DF3500889F2D /* EmptyTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D58B2413DF3500889F2D /* EmptyTransition.swift */; }; 45 | 6AD8D5902413EE8600889F2D /* AnimatedTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D58F2413EE8600889F2D /* AnimatedTransition.swift */; }; 46 | 6AD8D5932413EF0700889F2D /* FadeAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5922413EF0700889F2D /* FadeAnimatedTransitioning.swift */; }; 47 | 6AD8D5952413F04D00889F2D /* AnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5942413F04D00889F2D /* AnimatedTransitioning.swift */; }; 48 | 6AD8D5972413F5E900889F2D /* SiriRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5962413F5E900889F2D /* SiriRouter.swift */; }; 49 | 6AD8D5992413F7AA00889F2D /* SiriRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5982413F7AA00889F2D /* SiriRoute.swift */; }; 50 | 6AD8D5A124146A0F00889F2D /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5A024146A0F00889F2D /* IntentHandler.swift */; }; 51 | 6AD8D5AA24146A0F00889F2D /* IntentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AD8D5A924146A0F00889F2D /* IntentsUI.framework */; }; 52 | 6AD8D5AD24146A0F00889F2D /* IntentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5AC24146A0F00889F2D /* IntentViewController.swift */; }; 53 | 6AD8D5B024146A0F00889F2D /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6AD8D5AE24146A0F00889F2D /* MainInterface.storyboard */; }; 54 | 6AD8D5B424146A0F00889F2D /* AddToWishlistIntentUI.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6AD8D5A724146A0F00889F2D /* AddToWishlistIntentUI.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 55 | 6AD8D5B724146A0F00889F2D /* AddToWishlistIntent.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6AD8D59E24146A0F00889F2D /* AddToWishlistIntent.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 56 | 6AD8D5C124146A7A00889F2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5C024146A7A00889F2D /* Intents.intentdefinition */; }; 57 | 6AD8D5C224146A8100889F2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5C024146A7A00889F2D /* Intents.intentdefinition */; }; 58 | 6AD8D5C324146A8200889F2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5C024146A7A00889F2D /* Intents.intentdefinition */; }; 59 | 6AD8D5C524146CC300889F2D /* AddToWishlistIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5C424146CC300889F2D /* AddToWishlistIntentHandler.swift */; }; 60 | 6AD8D5C624146CC300889F2D /* AddToWishlistIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5C424146CC300889F2D /* AddToWishlistIntentHandler.swift */; }; 61 | 6AD8D5C82414BBAE00889F2D /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5C72414BBAE00889F2D /* UIViewController.swift */; }; 62 | 6AD8D5CA2414C2BC00889F2D /* DeeplinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD8D5C92414C2BC00889F2D /* DeeplinkRouter.swift */; }; 63 | /* End PBXBuildFile section */ 64 | 65 | /* Begin PBXContainerItemProxy section */ 66 | 6A61169724136AE50099C25F /* PBXContainerItemProxy */ = { 67 | isa = PBXContainerItemProxy; 68 | containerPortal = 6A61167624136AE30099C25F /* Project object */; 69 | proxyType = 1; 70 | remoteGlobalIDString = 6A61167D24136AE30099C25F; 71 | remoteInfo = RoutingExample; 72 | }; 73 | 6AD8D5B224146A0F00889F2D /* PBXContainerItemProxy */ = { 74 | isa = PBXContainerItemProxy; 75 | containerPortal = 6A61167624136AE30099C25F /* Project object */; 76 | proxyType = 1; 77 | remoteGlobalIDString = 6AD8D5A624146A0F00889F2D; 78 | remoteInfo = AddToWishlistIntentUI; 79 | }; 80 | 6AD8D5B524146A0F00889F2D /* PBXContainerItemProxy */ = { 81 | isa = PBXContainerItemProxy; 82 | containerPortal = 6A61167624136AE30099C25F /* Project object */; 83 | proxyType = 1; 84 | remoteGlobalIDString = 6AD8D59D24146A0F00889F2D; 85 | remoteInfo = AddToWishlistIntent; 86 | }; 87 | /* End PBXContainerItemProxy section */ 88 | 89 | /* Begin PBXCopyFilesBuildPhase section */ 90 | 6AD8D5BE24146A0F00889F2D /* Embed App Extensions */ = { 91 | isa = PBXCopyFilesBuildPhase; 92 | buildActionMask = 2147483647; 93 | dstPath = ""; 94 | dstSubfolderSpec = 13; 95 | files = ( 96 | 6AD8D5B724146A0F00889F2D /* AddToWishlistIntent.appex in Embed App Extensions */, 97 | 6AD8D5B424146A0F00889F2D /* AddToWishlistIntentUI.appex in Embed App Extensions */, 98 | ); 99 | name = "Embed App Extensions"; 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | /* End PBXCopyFilesBuildPhase section */ 103 | 104 | /* Begin PBXFileReference section */ 105 | 6A106788241CDA3B00844681 /* WishlistTabRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WishlistTabRoute.swift; sourceTree = ""; }; 106 | 6A106789241CDA3B00844681 /* ShopTabRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShopTabRoute.swift; sourceTree = ""; }; 107 | 6A61167E24136AE30099C25F /* RoutingExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RoutingExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 108 | 6A61168124136AE30099C25F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 109 | 6A61168324136AE30099C25F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 110 | 6A61168524136AE30099C25F /* ShopViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopViewController.swift; sourceTree = ""; }; 111 | 6A61168C24136AE50099C25F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 112 | 6A61168F24136AE50099C25F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 113 | 6A61169124136AE50099C25F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 114 | 6A61169624136AE50099C25F /* RoutingExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RoutingExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 115 | 6A61169A24136AE50099C25F /* RoutingExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingExampleTests.swift; sourceTree = ""; }; 116 | 6A61169C24136AE50099C25F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 117 | 6A6116A524136C5C0099C25F /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; 118 | 6A6116A724136EF30099C25F /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 119 | 6A6116A924136EFE0099C25F /* ProductViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductViewController.swift; sourceTree = ""; }; 120 | 6A6116AB24136F1B0099C25F /* ForgottenPasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgottenPasswordViewController.swift; sourceTree = ""; }; 121 | 6A6116AD24136F3B0099C25F /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; 122 | 6A6116AF24136F470099C25F /* PopUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpViewController.swift; sourceTree = ""; }; 123 | 6A6116B824136FFD0099C25F /* WishlistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WishlistViewController.swift; sourceTree = ""; }; 124 | 6A6116BC241372270099C25F /* LayoutHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutHelper.swift; sourceTree = ""; }; 125 | 6A6116BE241372C70099C25F /* DefaultButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultButton.swift; sourceTree = ""; }; 126 | 6AD8D5612413848700889F2D /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; 127 | 6AD8D5632413848700889F2D /* ModalTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModalTransition.swift; sourceTree = ""; }; 128 | 6AD8D5642413848700889F2D /* Transition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = ""; }; 129 | 6AD8D5652413848700889F2D /* PushTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushTransition.swift; sourceTree = ""; }; 130 | 6AD8D5702413853A00889F2D /* SignUpRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpRoute.swift; sourceTree = ""; }; 131 | 6AD8D5712413853B00889F2D /* LoginRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginRoute.swift; sourceTree = ""; }; 132 | 6AD8D5742413B16300889F2D /* ForgottenPasswordRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgottenPasswordRoute.swift; sourceTree = ""; }; 133 | 6AD8D5762413B24000889F2D /* PopUpRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpRoute.swift; sourceTree = ""; }; 134 | 6AD8D5782413B81C00889F2D /* ProductRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductRoute.swift; sourceTree = ""; }; 135 | 6AD8D57B2413D33D00889F2D /* DefaultRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRouter.swift; sourceTree = ""; }; 136 | 6AD8D57D2413D55300889F2D /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; 137 | 6AD8D57F2413D55F00889F2D /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; }; 138 | 6AD8D5812413D56C00889F2D /* ForgottenPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgottenPasswordViewModel.swift; sourceTree = ""; }; 139 | 6AD8D5832413D72400889F2D /* ShopViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopViewModel.swift; sourceTree = ""; }; 140 | 6AD8D5852413D73B00889F2D /* WishlistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WishlistViewModel.swift; sourceTree = ""; }; 141 | 6AD8D5872413D83900889F2D /* PopUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpViewModel.swift; sourceTree = ""; }; 142 | 6AD8D5892413D8C800889F2D /* ProductViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductViewModel.swift; sourceTree = ""; }; 143 | 6AD8D58B2413DF3500889F2D /* EmptyTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTransition.swift; sourceTree = ""; }; 144 | 6AD8D58F2413EE8600889F2D /* AnimatedTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedTransition.swift; sourceTree = ""; }; 145 | 6AD8D5922413EF0700889F2D /* FadeAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeAnimatedTransitioning.swift; sourceTree = ""; }; 146 | 6AD8D5942413F04D00889F2D /* AnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedTransitioning.swift; sourceTree = ""; }; 147 | 6AD8D5962413F5E900889F2D /* SiriRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiriRouter.swift; sourceTree = ""; }; 148 | 6AD8D5982413F7AA00889F2D /* SiriRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiriRoute.swift; sourceTree = ""; }; 149 | 6AD8D59E24146A0F00889F2D /* AddToWishlistIntent.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AddToWishlistIntent.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 150 | 6AD8D5A024146A0F00889F2D /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; 151 | 6AD8D5A224146A0F00889F2D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 152 | 6AD8D5A724146A0F00889F2D /* AddToWishlistIntentUI.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AddToWishlistIntentUI.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 153 | 6AD8D5A924146A0F00889F2D /* IntentsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntentsUI.framework; path = System/Library/Frameworks/IntentsUI.framework; sourceTree = SDKROOT; }; 154 | 6AD8D5AC24146A0F00889F2D /* IntentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentViewController.swift; sourceTree = ""; }; 155 | 6AD8D5AF24146A0F00889F2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 156 | 6AD8D5B124146A0F00889F2D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 157 | 6AD8D5C024146A7A00889F2D /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = ""; }; 158 | 6AD8D5C424146CC300889F2D /* AddToWishlistIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToWishlistIntentHandler.swift; sourceTree = ""; }; 159 | 6AD8D5C72414BBAE00889F2D /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; 160 | 6AD8D5C92414C2BC00889F2D /* DeeplinkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeeplinkRouter.swift; sourceTree = ""; }; 161 | /* End PBXFileReference section */ 162 | 163 | /* Begin PBXFrameworksBuildPhase section */ 164 | 6A61167B24136AE30099C25F /* Frameworks */ = { 165 | isa = PBXFrameworksBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | 6A61169324136AE50099C25F /* Frameworks */ = { 172 | isa = PBXFrameworksBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | 6AD8D59B24146A0F00889F2D /* Frameworks */ = { 179 | isa = PBXFrameworksBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | 6AD8D5A424146A0F00889F2D /* Frameworks */ = { 186 | isa = PBXFrameworksBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | 6AD8D5AA24146A0F00889F2D /* IntentsUI.framework in Frameworks */, 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | }; 193 | /* End PBXFrameworksBuildPhase section */ 194 | 195 | /* Begin PBXGroup section */ 196 | 6A106787241CDA3B00844681 /* Tabs */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 6A106788241CDA3B00844681 /* WishlistTabRoute.swift */, 200 | 6A106789241CDA3B00844681 /* ShopTabRoute.swift */, 201 | ); 202 | path = Tabs; 203 | sourceTree = ""; 204 | }; 205 | 6A61167524136AE30099C25F = { 206 | isa = PBXGroup; 207 | children = ( 208 | 6A61168024136AE30099C25F /* RoutingExample */, 209 | 6A61169924136AE50099C25F /* RoutingExampleTests */, 210 | 6AD8D59F24146A0F00889F2D /* AddToWishlistIntent */, 211 | 6AD8D5AB24146A0F00889F2D /* AddToWishlistIntentUI */, 212 | 6AD8D5A824146A0F00889F2D /* Frameworks */, 213 | 6A61167F24136AE30099C25F /* Products */, 214 | ); 215 | sourceTree = ""; 216 | }; 217 | 6A61167F24136AE30099C25F /* Products */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | 6A61167E24136AE30099C25F /* RoutingExample.app */, 221 | 6A61169624136AE50099C25F /* RoutingExampleTests.xctest */, 222 | 6AD8D59E24146A0F00889F2D /* AddToWishlistIntent.appex */, 223 | 6AD8D5A724146A0F00889F2D /* AddToWishlistIntentUI.appex */, 224 | ); 225 | name = Products; 226 | sourceTree = ""; 227 | }; 228 | 6A61168024136AE30099C25F /* RoutingExample */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | 6A61169124136AE50099C25F /* Info.plist */, 232 | 6A61168124136AE30099C25F /* AppDelegate.swift */, 233 | 6A61168324136AE30099C25F /* SceneDelegate.swift */, 234 | 6A61168C24136AE50099C25F /* Assets.xcassets */, 235 | 6A61168E24136AE50099C25F /* LaunchScreen.storyboard */, 236 | 6A6116B124136F6C0099C25F /* Authentication */, 237 | 6A6116B224136F7B0099C25F /* PopUps */, 238 | 6A6116B324136F840099C25F /* Product */, 239 | 6AD8D55F2413848700889F2D /* Routing */, 240 | 6A6116BA2413701E0099C25F /* Tabs */, 241 | 6A6116BB2413721C0099C25F /* Utils */, 242 | ); 243 | path = RoutingExample; 244 | sourceTree = ""; 245 | }; 246 | 6A61169924136AE50099C25F /* RoutingExampleTests */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 6A61169A24136AE50099C25F /* RoutingExampleTests.swift */, 250 | 6A61169C24136AE50099C25F /* Info.plist */, 251 | ); 252 | path = RoutingExampleTests; 253 | sourceTree = ""; 254 | }; 255 | 6A6116B124136F6C0099C25F /* Authentication */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 6A6116AB24136F1B0099C25F /* ForgottenPasswordViewController.swift */, 259 | 6AD8D5812413D56C00889F2D /* ForgottenPasswordViewModel.swift */, 260 | 6A6116A724136EF30099C25F /* LoginViewController.swift */, 261 | 6AD8D57D2413D55300889F2D /* LoginViewModel.swift */, 262 | 6A6116AD24136F3B0099C25F /* SignUpViewController.swift */, 263 | 6AD8D57F2413D55F00889F2D /* SignUpViewModel.swift */, 264 | ); 265 | path = Authentication; 266 | sourceTree = ""; 267 | }; 268 | 6A6116B224136F7B0099C25F /* PopUps */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | 6A6116AF24136F470099C25F /* PopUpViewController.swift */, 272 | 6AD8D5872413D83900889F2D /* PopUpViewModel.swift */, 273 | ); 274 | path = PopUps; 275 | sourceTree = ""; 276 | }; 277 | 6A6116B324136F840099C25F /* Product */ = { 278 | isa = PBXGroup; 279 | children = ( 280 | 6A6116A924136EFE0099C25F /* ProductViewController.swift */, 281 | 6AD8D5892413D8C800889F2D /* ProductViewModel.swift */, 282 | ); 283 | path = Product; 284 | sourceTree = ""; 285 | }; 286 | 6A6116BA2413701E0099C25F /* Tabs */ = { 287 | isa = PBXGroup; 288 | children = ( 289 | 6A6116A524136C5C0099C25F /* MainTabBarController.swift */, 290 | 6A61168524136AE30099C25F /* ShopViewController.swift */, 291 | 6AD8D5832413D72400889F2D /* ShopViewModel.swift */, 292 | 6A6116B824136FFD0099C25F /* WishlistViewController.swift */, 293 | 6AD8D5852413D73B00889F2D /* WishlistViewModel.swift */, 294 | ); 295 | path = Tabs; 296 | sourceTree = ""; 297 | }; 298 | 6A6116BB2413721C0099C25F /* Utils */ = { 299 | isa = PBXGroup; 300 | children = ( 301 | 6A6116BE241372C70099C25F /* DefaultButton.swift */, 302 | 6A6116BC241372270099C25F /* LayoutHelper.swift */, 303 | 6AD8D5C72414BBAE00889F2D /* UIViewController.swift */, 304 | ); 305 | path = Utils; 306 | sourceTree = ""; 307 | }; 308 | 6AD8D55F2413848700889F2D /* Routing */ = { 309 | isa = PBXGroup; 310 | children = ( 311 | 6AD8D5612413848700889F2D /* Router.swift */, 312 | 6AD8D5912413EEAB00889F2D /* Animator */, 313 | 6AD8D57A2413D32B00889F2D /* Routers */, 314 | 6AD8D5662413848700889F2D /* Routes */, 315 | 6AD8D5622413848700889F2D /* Transitions */, 316 | ); 317 | path = Routing; 318 | sourceTree = ""; 319 | }; 320 | 6AD8D5622413848700889F2D /* Transitions */ = { 321 | isa = PBXGroup; 322 | children = ( 323 | 6AD8D58F2413EE8600889F2D /* AnimatedTransition.swift */, 324 | 6AD8D58B2413DF3500889F2D /* EmptyTransition.swift */, 325 | 6AD8D5632413848700889F2D /* ModalTransition.swift */, 326 | 6AD8D5652413848700889F2D /* PushTransition.swift */, 327 | 6AD8D5642413848700889F2D /* Transition.swift */, 328 | ); 329 | path = Transitions; 330 | sourceTree = ""; 331 | }; 332 | 6AD8D5662413848700889F2D /* Routes */ = { 333 | isa = PBXGroup; 334 | children = ( 335 | 6AD8D5742413B16300889F2D /* ForgottenPasswordRoute.swift */, 336 | 6AD8D5712413853B00889F2D /* LoginRoute.swift */, 337 | 6AD8D5762413B24000889F2D /* PopUpRoute.swift */, 338 | 6AD8D5782413B81C00889F2D /* ProductRoute.swift */, 339 | 6AD8D5702413853A00889F2D /* SignUpRoute.swift */, 340 | 6AD8D5982413F7AA00889F2D /* SiriRoute.swift */, 341 | 6A106787241CDA3B00844681 /* Tabs */, 342 | ); 343 | path = Routes; 344 | sourceTree = ""; 345 | }; 346 | 6AD8D57A2413D32B00889F2D /* Routers */ = { 347 | isa = PBXGroup; 348 | children = ( 349 | 6AD8D5C92414C2BC00889F2D /* DeeplinkRouter.swift */, 350 | 6AD8D57B2413D33D00889F2D /* DefaultRouter.swift */, 351 | 6AD8D5962413F5E900889F2D /* SiriRouter.swift */, 352 | ); 353 | path = Routers; 354 | sourceTree = ""; 355 | }; 356 | 6AD8D5912413EEAB00889F2D /* Animator */ = { 357 | isa = PBXGroup; 358 | children = ( 359 | 6AD8D5942413F04D00889F2D /* AnimatedTransitioning.swift */, 360 | 6AD8D5922413EF0700889F2D /* FadeAnimatedTransitioning.swift */, 361 | ); 362 | path = Animator; 363 | sourceTree = ""; 364 | }; 365 | 6AD8D59F24146A0F00889F2D /* AddToWishlistIntent */ = { 366 | isa = PBXGroup; 367 | children = ( 368 | 6AD8D5C024146A7A00889F2D /* Intents.intentdefinition */, 369 | 6AD8D5A224146A0F00889F2D /* Info.plist */, 370 | 6AD8D5C424146CC300889F2D /* AddToWishlistIntentHandler.swift */, 371 | 6AD8D5A024146A0F00889F2D /* IntentHandler.swift */, 372 | ); 373 | path = AddToWishlistIntent; 374 | sourceTree = ""; 375 | }; 376 | 6AD8D5A824146A0F00889F2D /* Frameworks */ = { 377 | isa = PBXGroup; 378 | children = ( 379 | 6AD8D5A924146A0F00889F2D /* IntentsUI.framework */, 380 | ); 381 | name = Frameworks; 382 | sourceTree = ""; 383 | }; 384 | 6AD8D5AB24146A0F00889F2D /* AddToWishlistIntentUI */ = { 385 | isa = PBXGroup; 386 | children = ( 387 | 6AD8D5AC24146A0F00889F2D /* IntentViewController.swift */, 388 | 6AD8D5AE24146A0F00889F2D /* MainInterface.storyboard */, 389 | 6AD8D5B124146A0F00889F2D /* Info.plist */, 390 | ); 391 | path = AddToWishlistIntentUI; 392 | sourceTree = ""; 393 | }; 394 | /* End PBXGroup section */ 395 | 396 | /* Begin PBXNativeTarget section */ 397 | 6A61167D24136AE30099C25F /* RoutingExample */ = { 398 | isa = PBXNativeTarget; 399 | buildConfigurationList = 6A61169F24136AE50099C25F /* Build configuration list for PBXNativeTarget "RoutingExample" */; 400 | buildPhases = ( 401 | 6A61167A24136AE30099C25F /* Sources */, 402 | 6A61167B24136AE30099C25F /* Frameworks */, 403 | 6A61167C24136AE30099C25F /* Resources */, 404 | 6AD8D5BE24146A0F00889F2D /* Embed App Extensions */, 405 | ); 406 | buildRules = ( 407 | ); 408 | dependencies = ( 409 | 6AD8D5B324146A0F00889F2D /* PBXTargetDependency */, 410 | 6AD8D5B624146A0F00889F2D /* PBXTargetDependency */, 411 | ); 412 | name = RoutingExample; 413 | productName = RoutingExample; 414 | productReference = 6A61167E24136AE30099C25F /* RoutingExample.app */; 415 | productType = "com.apple.product-type.application"; 416 | }; 417 | 6A61169524136AE50099C25F /* RoutingExampleTests */ = { 418 | isa = PBXNativeTarget; 419 | buildConfigurationList = 6A6116A224136AE50099C25F /* Build configuration list for PBXNativeTarget "RoutingExampleTests" */; 420 | buildPhases = ( 421 | 6A61169224136AE50099C25F /* Sources */, 422 | 6A61169324136AE50099C25F /* Frameworks */, 423 | 6A61169424136AE50099C25F /* Resources */, 424 | ); 425 | buildRules = ( 426 | ); 427 | dependencies = ( 428 | 6A61169824136AE50099C25F /* PBXTargetDependency */, 429 | ); 430 | name = RoutingExampleTests; 431 | productName = RoutingExampleTests; 432 | productReference = 6A61169624136AE50099C25F /* RoutingExampleTests.xctest */; 433 | productType = "com.apple.product-type.bundle.unit-test"; 434 | }; 435 | 6AD8D59D24146A0F00889F2D /* AddToWishlistIntent */ = { 436 | isa = PBXNativeTarget; 437 | buildConfigurationList = 6AD8D5BB24146A0F00889F2D /* Build configuration list for PBXNativeTarget "AddToWishlistIntent" */; 438 | buildPhases = ( 439 | 6AD8D59A24146A0F00889F2D /* Sources */, 440 | 6AD8D59B24146A0F00889F2D /* Frameworks */, 441 | 6AD8D59C24146A0F00889F2D /* Resources */, 442 | ); 443 | buildRules = ( 444 | ); 445 | dependencies = ( 446 | ); 447 | name = AddToWishlistIntent; 448 | productName = AddToWishlistIntent; 449 | productReference = 6AD8D59E24146A0F00889F2D /* AddToWishlistIntent.appex */; 450 | productType = "com.apple.product-type.app-extension"; 451 | }; 452 | 6AD8D5A624146A0F00889F2D /* AddToWishlistIntentUI */ = { 453 | isa = PBXNativeTarget; 454 | buildConfigurationList = 6AD8D5B824146A0F00889F2D /* Build configuration list for PBXNativeTarget "AddToWishlistIntentUI" */; 455 | buildPhases = ( 456 | 6AD8D5A324146A0F00889F2D /* Sources */, 457 | 6AD8D5A424146A0F00889F2D /* Frameworks */, 458 | 6AD8D5A524146A0F00889F2D /* Resources */, 459 | ); 460 | buildRules = ( 461 | ); 462 | dependencies = ( 463 | ); 464 | name = AddToWishlistIntentUI; 465 | productName = AddToWishlistIntentUI; 466 | productReference = 6AD8D5A724146A0F00889F2D /* AddToWishlistIntentUI.appex */; 467 | productType = "com.apple.product-type.app-extension"; 468 | }; 469 | /* End PBXNativeTarget section */ 470 | 471 | /* Begin PBXProject section */ 472 | 6A61167624136AE30099C25F /* Project object */ = { 473 | isa = PBXProject; 474 | attributes = { 475 | LastSwiftUpdateCheck = 1130; 476 | LastUpgradeCheck = 1130; 477 | ORGANIZATIONNAME = "Cassius Pacheco"; 478 | TargetAttributes = { 479 | 6A61167D24136AE30099C25F = { 480 | CreatedOnToolsVersion = 11.3.1; 481 | }; 482 | 6A61169524136AE50099C25F = { 483 | CreatedOnToolsVersion = 11.3.1; 484 | TestTargetID = 6A61167D24136AE30099C25F; 485 | }; 486 | 6AD8D59D24146A0F00889F2D = { 487 | CreatedOnToolsVersion = 11.3.1; 488 | }; 489 | 6AD8D5A624146A0F00889F2D = { 490 | CreatedOnToolsVersion = 11.3.1; 491 | }; 492 | }; 493 | }; 494 | buildConfigurationList = 6A61167924136AE30099C25F /* Build configuration list for PBXProject "RoutingExample" */; 495 | compatibilityVersion = "Xcode 9.3"; 496 | developmentRegion = en; 497 | hasScannedForEncodings = 0; 498 | knownRegions = ( 499 | en, 500 | Base, 501 | ); 502 | mainGroup = 6A61167524136AE30099C25F; 503 | productRefGroup = 6A61167F24136AE30099C25F /* Products */; 504 | projectDirPath = ""; 505 | projectRoot = ""; 506 | targets = ( 507 | 6A61167D24136AE30099C25F /* RoutingExample */, 508 | 6A61169524136AE50099C25F /* RoutingExampleTests */, 509 | 6AD8D59D24146A0F00889F2D /* AddToWishlistIntent */, 510 | 6AD8D5A624146A0F00889F2D /* AddToWishlistIntentUI */, 511 | ); 512 | }; 513 | /* End PBXProject section */ 514 | 515 | /* Begin PBXResourcesBuildPhase section */ 516 | 6A61167C24136AE30099C25F /* Resources */ = { 517 | isa = PBXResourcesBuildPhase; 518 | buildActionMask = 2147483647; 519 | files = ( 520 | 6A61169024136AE50099C25F /* LaunchScreen.storyboard in Resources */, 521 | 6A61168D24136AE50099C25F /* Assets.xcassets in Resources */, 522 | ); 523 | runOnlyForDeploymentPostprocessing = 0; 524 | }; 525 | 6A61169424136AE50099C25F /* Resources */ = { 526 | isa = PBXResourcesBuildPhase; 527 | buildActionMask = 2147483647; 528 | files = ( 529 | ); 530 | runOnlyForDeploymentPostprocessing = 0; 531 | }; 532 | 6AD8D59C24146A0F00889F2D /* Resources */ = { 533 | isa = PBXResourcesBuildPhase; 534 | buildActionMask = 2147483647; 535 | files = ( 536 | ); 537 | runOnlyForDeploymentPostprocessing = 0; 538 | }; 539 | 6AD8D5A524146A0F00889F2D /* Resources */ = { 540 | isa = PBXResourcesBuildPhase; 541 | buildActionMask = 2147483647; 542 | files = ( 543 | 6AD8D5B024146A0F00889F2D /* MainInterface.storyboard in Resources */, 544 | ); 545 | runOnlyForDeploymentPostprocessing = 0; 546 | }; 547 | /* End PBXResourcesBuildPhase section */ 548 | 549 | /* Begin PBXSourcesBuildPhase section */ 550 | 6A61167A24136AE30099C25F /* Sources */ = { 551 | isa = PBXSourcesBuildPhase; 552 | buildActionMask = 2147483647; 553 | files = ( 554 | 6AD8D57E2413D55300889F2D /* LoginViewModel.swift in Sources */, 555 | 6A6116A824136EF30099C25F /* LoginViewController.swift in Sources */, 556 | 6AD8D5972413F5E900889F2D /* SiriRouter.swift in Sources */, 557 | 6AD8D58C2413DF3500889F2D /* EmptyTransition.swift in Sources */, 558 | 6A6116AC24136F1B0099C25F /* ForgottenPasswordViewController.swift in Sources */, 559 | 6AD8D5CA2414C2BC00889F2D /* DeeplinkRouter.swift in Sources */, 560 | 6AD8D58A2413D8C800889F2D /* ProductViewModel.swift in Sources */, 561 | 6A61168624136AE30099C25F /* ShopViewController.swift in Sources */, 562 | 6AD8D5722413853B00889F2D /* SignUpRoute.swift in Sources */, 563 | 6AD8D57C2413D33D00889F2D /* DefaultRouter.swift in Sources */, 564 | 6AD8D5732413853B00889F2D /* LoginRoute.swift in Sources */, 565 | 6AD8D56B2413848700889F2D /* ModalTransition.swift in Sources */, 566 | 6A6116AA24136EFE0099C25F /* ProductViewController.swift in Sources */, 567 | 6AD8D5952413F04D00889F2D /* AnimatedTransitioning.swift in Sources */, 568 | 6A6116B024136F470099C25F /* PopUpViewController.swift in Sources */, 569 | 6AD8D5C524146CC300889F2D /* AddToWishlistIntentHandler.swift in Sources */, 570 | 6AD8D5802413D55F00889F2D /* SignUpViewModel.swift in Sources */, 571 | 6AD8D56C2413848700889F2D /* Transition.swift in Sources */, 572 | 6A6116A624136C5C0099C25F /* MainTabBarController.swift in Sources */, 573 | 6A6116BD241372270099C25F /* LayoutHelper.swift in Sources */, 574 | 6AD8D5862413D73B00889F2D /* WishlistViewModel.swift in Sources */, 575 | 6AD8D56D2413848700889F2D /* PushTransition.swift in Sources */, 576 | 6A10678A241CDA3B00844681 /* WishlistTabRoute.swift in Sources */, 577 | 6AD8D5992413F7AA00889F2D /* SiriRoute.swift in Sources */, 578 | 6AD8D5C82414BBAE00889F2D /* UIViewController.swift in Sources */, 579 | 6AD8D5752413B16300889F2D /* ForgottenPasswordRoute.swift in Sources */, 580 | 6A6116AE24136F3B0099C25F /* SignUpViewController.swift in Sources */, 581 | 6AD8D5822413D56C00889F2D /* ForgottenPasswordViewModel.swift in Sources */, 582 | 6A6116B924136FFD0099C25F /* WishlistViewController.swift in Sources */, 583 | 6AD8D5932413EF0700889F2D /* FadeAnimatedTransitioning.swift in Sources */, 584 | 6AD8D5902413EE8600889F2D /* AnimatedTransition.swift in Sources */, 585 | 6AD8D56A2413848700889F2D /* Router.swift in Sources */, 586 | 6AD8D5842413D72400889F2D /* ShopViewModel.swift in Sources */, 587 | 6A61168224136AE30099C25F /* AppDelegate.swift in Sources */, 588 | 6AD8D5C124146A7A00889F2D /* Intents.intentdefinition in Sources */, 589 | 6AD8D5882413D83900889F2D /* PopUpViewModel.swift in Sources */, 590 | 6A61168424136AE30099C25F /* SceneDelegate.swift in Sources */, 591 | 6AD8D5772413B24000889F2D /* PopUpRoute.swift in Sources */, 592 | 6A10678B241CDA3B00844681 /* ShopTabRoute.swift in Sources */, 593 | 6A6116BF241372C70099C25F /* DefaultButton.swift in Sources */, 594 | 6AD8D5792413B81C00889F2D /* ProductRoute.swift in Sources */, 595 | ); 596 | runOnlyForDeploymentPostprocessing = 0; 597 | }; 598 | 6A61169224136AE50099C25F /* Sources */ = { 599 | isa = PBXSourcesBuildPhase; 600 | buildActionMask = 2147483647; 601 | files = ( 602 | 6A61169B24136AE50099C25F /* RoutingExampleTests.swift in Sources */, 603 | ); 604 | runOnlyForDeploymentPostprocessing = 0; 605 | }; 606 | 6AD8D59A24146A0F00889F2D /* Sources */ = { 607 | isa = PBXSourcesBuildPhase; 608 | buildActionMask = 2147483647; 609 | files = ( 610 | 6AD8D5C624146CC300889F2D /* AddToWishlistIntentHandler.swift in Sources */, 611 | 6AD8D5C224146A8100889F2D /* Intents.intentdefinition in Sources */, 612 | 6AD8D5A124146A0F00889F2D /* IntentHandler.swift in Sources */, 613 | ); 614 | runOnlyForDeploymentPostprocessing = 0; 615 | }; 616 | 6AD8D5A324146A0F00889F2D /* Sources */ = { 617 | isa = PBXSourcesBuildPhase; 618 | buildActionMask = 2147483647; 619 | files = ( 620 | 6AD8D5C324146A8200889F2D /* Intents.intentdefinition in Sources */, 621 | 6AD8D5AD24146A0F00889F2D /* IntentViewController.swift in Sources */, 622 | ); 623 | runOnlyForDeploymentPostprocessing = 0; 624 | }; 625 | /* End PBXSourcesBuildPhase section */ 626 | 627 | /* Begin PBXTargetDependency section */ 628 | 6A61169824136AE50099C25F /* PBXTargetDependency */ = { 629 | isa = PBXTargetDependency; 630 | target = 6A61167D24136AE30099C25F /* RoutingExample */; 631 | targetProxy = 6A61169724136AE50099C25F /* PBXContainerItemProxy */; 632 | }; 633 | 6AD8D5B324146A0F00889F2D /* PBXTargetDependency */ = { 634 | isa = PBXTargetDependency; 635 | target = 6AD8D5A624146A0F00889F2D /* AddToWishlistIntentUI */; 636 | targetProxy = 6AD8D5B224146A0F00889F2D /* PBXContainerItemProxy */; 637 | }; 638 | 6AD8D5B624146A0F00889F2D /* PBXTargetDependency */ = { 639 | isa = PBXTargetDependency; 640 | target = 6AD8D59D24146A0F00889F2D /* AddToWishlistIntent */; 641 | targetProxy = 6AD8D5B524146A0F00889F2D /* PBXContainerItemProxy */; 642 | }; 643 | /* End PBXTargetDependency section */ 644 | 645 | /* Begin PBXVariantGroup section */ 646 | 6A61168E24136AE50099C25F /* LaunchScreen.storyboard */ = { 647 | isa = PBXVariantGroup; 648 | children = ( 649 | 6A61168F24136AE50099C25F /* Base */, 650 | ); 651 | name = LaunchScreen.storyboard; 652 | sourceTree = ""; 653 | }; 654 | 6AD8D5AE24146A0F00889F2D /* MainInterface.storyboard */ = { 655 | isa = PBXVariantGroup; 656 | children = ( 657 | 6AD8D5AF24146A0F00889F2D /* Base */, 658 | ); 659 | name = MainInterface.storyboard; 660 | sourceTree = ""; 661 | }; 662 | /* End PBXVariantGroup section */ 663 | 664 | /* Begin XCBuildConfiguration section */ 665 | 6A61169D24136AE50099C25F /* Debug */ = { 666 | isa = XCBuildConfiguration; 667 | buildSettings = { 668 | ALWAYS_SEARCH_USER_PATHS = NO; 669 | CLANG_ANALYZER_NONNULL = YES; 670 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 671 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 672 | CLANG_CXX_LIBRARY = "libc++"; 673 | CLANG_ENABLE_MODULES = YES; 674 | CLANG_ENABLE_OBJC_ARC = YES; 675 | CLANG_ENABLE_OBJC_WEAK = YES; 676 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 677 | CLANG_WARN_BOOL_CONVERSION = YES; 678 | CLANG_WARN_COMMA = YES; 679 | CLANG_WARN_CONSTANT_CONVERSION = YES; 680 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 681 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 682 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 683 | CLANG_WARN_EMPTY_BODY = YES; 684 | CLANG_WARN_ENUM_CONVERSION = YES; 685 | CLANG_WARN_INFINITE_RECURSION = YES; 686 | CLANG_WARN_INT_CONVERSION = YES; 687 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 688 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 689 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 690 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 691 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 692 | CLANG_WARN_STRICT_PROTOTYPES = YES; 693 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 694 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 695 | CLANG_WARN_UNREACHABLE_CODE = YES; 696 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 697 | COPY_PHASE_STRIP = NO; 698 | DEBUG_INFORMATION_FORMAT = dwarf; 699 | ENABLE_STRICT_OBJC_MSGSEND = YES; 700 | ENABLE_TESTABILITY = YES; 701 | GCC_C_LANGUAGE_STANDARD = gnu11; 702 | GCC_DYNAMIC_NO_PIC = NO; 703 | GCC_NO_COMMON_BLOCKS = YES; 704 | GCC_OPTIMIZATION_LEVEL = 0; 705 | GCC_PREPROCESSOR_DEFINITIONS = ( 706 | "DEBUG=1", 707 | "$(inherited)", 708 | ); 709 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 710 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 711 | GCC_WARN_UNDECLARED_SELECTOR = YES; 712 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 713 | GCC_WARN_UNUSED_FUNCTION = YES; 714 | GCC_WARN_UNUSED_VARIABLE = YES; 715 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 716 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 717 | MTL_FAST_MATH = YES; 718 | ONLY_ACTIVE_ARCH = YES; 719 | SDKROOT = iphoneos; 720 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 721 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 722 | }; 723 | name = Debug; 724 | }; 725 | 6A61169E24136AE50099C25F /* Release */ = { 726 | isa = XCBuildConfiguration; 727 | buildSettings = { 728 | ALWAYS_SEARCH_USER_PATHS = NO; 729 | CLANG_ANALYZER_NONNULL = YES; 730 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 731 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 732 | CLANG_CXX_LIBRARY = "libc++"; 733 | CLANG_ENABLE_MODULES = YES; 734 | CLANG_ENABLE_OBJC_ARC = YES; 735 | CLANG_ENABLE_OBJC_WEAK = YES; 736 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 737 | CLANG_WARN_BOOL_CONVERSION = YES; 738 | CLANG_WARN_COMMA = YES; 739 | CLANG_WARN_CONSTANT_CONVERSION = YES; 740 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 741 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 742 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 743 | CLANG_WARN_EMPTY_BODY = YES; 744 | CLANG_WARN_ENUM_CONVERSION = YES; 745 | CLANG_WARN_INFINITE_RECURSION = YES; 746 | CLANG_WARN_INT_CONVERSION = YES; 747 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 748 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 749 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 750 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 751 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 752 | CLANG_WARN_STRICT_PROTOTYPES = YES; 753 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 754 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 755 | CLANG_WARN_UNREACHABLE_CODE = YES; 756 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 757 | COPY_PHASE_STRIP = NO; 758 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 759 | ENABLE_NS_ASSERTIONS = NO; 760 | ENABLE_STRICT_OBJC_MSGSEND = YES; 761 | GCC_C_LANGUAGE_STANDARD = gnu11; 762 | GCC_NO_COMMON_BLOCKS = YES; 763 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 764 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 765 | GCC_WARN_UNDECLARED_SELECTOR = YES; 766 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 767 | GCC_WARN_UNUSED_FUNCTION = YES; 768 | GCC_WARN_UNUSED_VARIABLE = YES; 769 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 770 | MTL_ENABLE_DEBUG_INFO = NO; 771 | MTL_FAST_MATH = YES; 772 | SDKROOT = iphoneos; 773 | SWIFT_COMPILATION_MODE = wholemodule; 774 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 775 | VALIDATE_PRODUCT = YES; 776 | }; 777 | name = Release; 778 | }; 779 | 6A6116A024136AE50099C25F /* Debug */ = { 780 | isa = XCBuildConfiguration; 781 | buildSettings = { 782 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 783 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 784 | CODE_SIGN_STYLE = Automatic; 785 | DEVELOPMENT_TEAM = 2LT888MY4H; 786 | INFOPLIST_FILE = RoutingExample/Info.plist; 787 | LD_RUNPATH_SEARCH_PATHS = ( 788 | "$(inherited)", 789 | "@executable_path/Frameworks", 790 | ); 791 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExample; 792 | PRODUCT_NAME = "$(TARGET_NAME)"; 793 | SWIFT_VERSION = 5.0; 794 | TARGETED_DEVICE_FAMILY = "1,2"; 795 | }; 796 | name = Debug; 797 | }; 798 | 6A6116A124136AE50099C25F /* Release */ = { 799 | isa = XCBuildConfiguration; 800 | buildSettings = { 801 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 802 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 803 | CODE_SIGN_STYLE = Automatic; 804 | DEVELOPMENT_TEAM = 2LT888MY4H; 805 | INFOPLIST_FILE = RoutingExample/Info.plist; 806 | LD_RUNPATH_SEARCH_PATHS = ( 807 | "$(inherited)", 808 | "@executable_path/Frameworks", 809 | ); 810 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExample; 811 | PRODUCT_NAME = "$(TARGET_NAME)"; 812 | SWIFT_VERSION = 5.0; 813 | TARGETED_DEVICE_FAMILY = "1,2"; 814 | }; 815 | name = Release; 816 | }; 817 | 6A6116A324136AE50099C25F /* Debug */ = { 818 | isa = XCBuildConfiguration; 819 | buildSettings = { 820 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 821 | BUNDLE_LOADER = "$(TEST_HOST)"; 822 | CODE_SIGN_STYLE = Automatic; 823 | DEVELOPMENT_TEAM = 2LT888MY4H; 824 | INFOPLIST_FILE = RoutingExampleTests/Info.plist; 825 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 826 | LD_RUNPATH_SEARCH_PATHS = ( 827 | "$(inherited)", 828 | "@executable_path/Frameworks", 829 | "@loader_path/Frameworks", 830 | ); 831 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExampleTests; 832 | PRODUCT_NAME = "$(TARGET_NAME)"; 833 | SWIFT_VERSION = 5.0; 834 | TARGETED_DEVICE_FAMILY = "1,2"; 835 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RoutingExample.app/RoutingExample"; 836 | }; 837 | name = Debug; 838 | }; 839 | 6A6116A424136AE50099C25F /* Release */ = { 840 | isa = XCBuildConfiguration; 841 | buildSettings = { 842 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 843 | BUNDLE_LOADER = "$(TEST_HOST)"; 844 | CODE_SIGN_STYLE = Automatic; 845 | DEVELOPMENT_TEAM = 2LT888MY4H; 846 | INFOPLIST_FILE = RoutingExampleTests/Info.plist; 847 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 848 | LD_RUNPATH_SEARCH_PATHS = ( 849 | "$(inherited)", 850 | "@executable_path/Frameworks", 851 | "@loader_path/Frameworks", 852 | ); 853 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExampleTests; 854 | PRODUCT_NAME = "$(TARGET_NAME)"; 855 | SWIFT_VERSION = 5.0; 856 | TARGETED_DEVICE_FAMILY = "1,2"; 857 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RoutingExample.app/RoutingExample"; 858 | }; 859 | name = Release; 860 | }; 861 | 6AD8D5B924146A0F00889F2D /* Debug */ = { 862 | isa = XCBuildConfiguration; 863 | buildSettings = { 864 | CODE_SIGN_STYLE = Automatic; 865 | DEVELOPMENT_TEAM = 2LT888MY4H; 866 | INFOPLIST_FILE = AddToWishlistIntentUI/Info.plist; 867 | LD_RUNPATH_SEARCH_PATHS = ( 868 | "$(inherited)", 869 | "@executable_path/Frameworks", 870 | "@executable_path/../../Frameworks", 871 | ); 872 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExample.AddToWishlistIntentUI; 873 | PRODUCT_NAME = "$(TARGET_NAME)"; 874 | SKIP_INSTALL = YES; 875 | SWIFT_VERSION = 5.0; 876 | TARGETED_DEVICE_FAMILY = "1,2"; 877 | }; 878 | name = Debug; 879 | }; 880 | 6AD8D5BA24146A0F00889F2D /* Release */ = { 881 | isa = XCBuildConfiguration; 882 | buildSettings = { 883 | CODE_SIGN_STYLE = Automatic; 884 | DEVELOPMENT_TEAM = 2LT888MY4H; 885 | INFOPLIST_FILE = AddToWishlistIntentUI/Info.plist; 886 | LD_RUNPATH_SEARCH_PATHS = ( 887 | "$(inherited)", 888 | "@executable_path/Frameworks", 889 | "@executable_path/../../Frameworks", 890 | ); 891 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExample.AddToWishlistIntentUI; 892 | PRODUCT_NAME = "$(TARGET_NAME)"; 893 | SKIP_INSTALL = YES; 894 | SWIFT_VERSION = 5.0; 895 | TARGETED_DEVICE_FAMILY = "1,2"; 896 | }; 897 | name = Release; 898 | }; 899 | 6AD8D5BC24146A0F00889F2D /* Debug */ = { 900 | isa = XCBuildConfiguration; 901 | buildSettings = { 902 | CODE_SIGN_STYLE = Automatic; 903 | DEVELOPMENT_TEAM = 2LT888MY4H; 904 | INFOPLIST_FILE = AddToWishlistIntent/Info.plist; 905 | LD_RUNPATH_SEARCH_PATHS = ( 906 | "$(inherited)", 907 | "@executable_path/Frameworks", 908 | "@executable_path/../../Frameworks", 909 | ); 910 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExample.AddToWishlistIntent; 911 | PRODUCT_NAME = "$(TARGET_NAME)"; 912 | SKIP_INSTALL = YES; 913 | SWIFT_VERSION = 5.0; 914 | TARGETED_DEVICE_FAMILY = "1,2"; 915 | }; 916 | name = Debug; 917 | }; 918 | 6AD8D5BD24146A0F00889F2D /* Release */ = { 919 | isa = XCBuildConfiguration; 920 | buildSettings = { 921 | CODE_SIGN_STYLE = Automatic; 922 | DEVELOPMENT_TEAM = 2LT888MY4H; 923 | INFOPLIST_FILE = AddToWishlistIntent/Info.plist; 924 | LD_RUNPATH_SEARCH_PATHS = ( 925 | "$(inherited)", 926 | "@executable_path/Frameworks", 927 | "@executable_path/../../Frameworks", 928 | ); 929 | PRODUCT_BUNDLE_IDENTIFIER = com.cassiuspacheco.RoutingExample.AddToWishlistIntent; 930 | PRODUCT_NAME = "$(TARGET_NAME)"; 931 | SKIP_INSTALL = YES; 932 | SWIFT_VERSION = 5.0; 933 | TARGETED_DEVICE_FAMILY = "1,2"; 934 | }; 935 | name = Release; 936 | }; 937 | /* End XCBuildConfiguration section */ 938 | 939 | /* Begin XCConfigurationList section */ 940 | 6A61167924136AE30099C25F /* Build configuration list for PBXProject "RoutingExample" */ = { 941 | isa = XCConfigurationList; 942 | buildConfigurations = ( 943 | 6A61169D24136AE50099C25F /* Debug */, 944 | 6A61169E24136AE50099C25F /* Release */, 945 | ); 946 | defaultConfigurationIsVisible = 0; 947 | defaultConfigurationName = Release; 948 | }; 949 | 6A61169F24136AE50099C25F /* Build configuration list for PBXNativeTarget "RoutingExample" */ = { 950 | isa = XCConfigurationList; 951 | buildConfigurations = ( 952 | 6A6116A024136AE50099C25F /* Debug */, 953 | 6A6116A124136AE50099C25F /* Release */, 954 | ); 955 | defaultConfigurationIsVisible = 0; 956 | defaultConfigurationName = Release; 957 | }; 958 | 6A6116A224136AE50099C25F /* Build configuration list for PBXNativeTarget "RoutingExampleTests" */ = { 959 | isa = XCConfigurationList; 960 | buildConfigurations = ( 961 | 6A6116A324136AE50099C25F /* Debug */, 962 | 6A6116A424136AE50099C25F /* Release */, 963 | ); 964 | defaultConfigurationIsVisible = 0; 965 | defaultConfigurationName = Release; 966 | }; 967 | 6AD8D5B824146A0F00889F2D /* Build configuration list for PBXNativeTarget "AddToWishlistIntentUI" */ = { 968 | isa = XCConfigurationList; 969 | buildConfigurations = ( 970 | 6AD8D5B924146A0F00889F2D /* Debug */, 971 | 6AD8D5BA24146A0F00889F2D /* Release */, 972 | ); 973 | defaultConfigurationIsVisible = 0; 974 | defaultConfigurationName = Release; 975 | }; 976 | 6AD8D5BB24146A0F00889F2D /* Build configuration list for PBXNativeTarget "AddToWishlistIntent" */ = { 977 | isa = XCConfigurationList; 978 | buildConfigurations = ( 979 | 6AD8D5BC24146A0F00889F2D /* Debug */, 980 | 6AD8D5BD24146A0F00889F2D /* Release */, 981 | ); 982 | defaultConfigurationIsVisible = 0; 983 | defaultConfigurationName = Release; 984 | }; 985 | /* End XCConfigurationList section */ 986 | }; 987 | rootObject = 6A61167624136AE30099C25F /* Project object */; 988 | } 989 | -------------------------------------------------------------------------------- /RoutingExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RoutingExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RoutingExample.xcodeproj/xcshareddata/xcschemes/RoutingExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /RoutingExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate {} 13 | -------------------------------------------------------------------------------- /RoutingExample/Assets.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 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /RoutingExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RoutingExample/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /RoutingExample/Assets.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CassiusPacheco/iOS-Routing-Example/54f2e214efb00f058dc91863db38ad9591bb1cdc/RoutingExample/Assets.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /RoutingExample/Assets.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /RoutingExample/Assets.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CassiusPacheco/iOS-Routing-Example/54f2e214efb00f058dc91863db38ad9591bb1cdc/RoutingExample/Assets.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /RoutingExample/Authentication/ForgottenPasswordViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForgottenPasswordViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ForgottenPasswordViewController: UIViewController { 12 | private let viewModel: ForgottenPasswordViewModel 13 | 14 | init(viewModel: ForgottenPasswordViewModel) { 15 | self.viewModel = viewModel 16 | super.init(nibName: nil, bundle: nil) 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | view.backgroundColor = .white 26 | 27 | print("👶 \(self) Created") 28 | 29 | let resetPasswordButton = DefaultButton(title: "Reset password", target: self, selector: #selector(resetPasswordButtonTouchUpInside)) 30 | view.addSubview(resetPasswordButton) 31 | resetPasswordButton.layout.center(in: view) 32 | } 33 | 34 | @objc 35 | private func resetPasswordButtonTouchUpInside() { 36 | viewModel.resetPasswordButtonTouchUpInside() 37 | } 38 | 39 | deinit { 40 | print("♻️ \(self) Deallocated") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /RoutingExample/Authentication/ForgottenPasswordViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForgottenPasswordViewModel.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class ForgottenPasswordViewModel { 12 | typealias Routes = PopUpRoute 13 | private let router: Routes 14 | 15 | init(router: Routes) { 16 | self.router = router 17 | } 18 | 19 | func resetPasswordButtonTouchUpInside() { 20 | print("Reset Password Button pressed") 21 | router.openPopUp(withMessage: "E-mail sent") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RoutingExample/Authentication/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class LoginViewController: UIViewController { 12 | private let viewModel: LoginViewModel 13 | 14 | init(viewModel: LoginViewModel) { 15 | self.viewModel = viewModel 16 | super.init(nibName: nil, bundle: nil) 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | view.backgroundColor = .white 26 | 27 | print("👶 \(self) Created") 28 | 29 | let dismissButton = DefaultButton(title: "Dismiss", target: self, selector: #selector(dismissButtonTouchUpInside)) 30 | let forgottenPasswordButton = DefaultButton(title: "Forgot Password", target: self, selector: #selector(forgottenPasswordButtonTouchUpInside)) 31 | let signUpButton = DefaultButton(title: "Sign Up", target: self, selector: #selector(signUpButtonTouchUpInside)) 32 | 33 | let vStack = UIStackView(arrangedSubviews: [dismissButton, forgottenPasswordButton, signUpButton]) 34 | vStack.axis = .vertical 35 | vStack.spacing = 8.0 36 | 37 | view.addSubview(vStack) 38 | vStack.layout.center(in: view) 39 | } 40 | 41 | @objc 42 | private func dismissButtonTouchUpInside() { 43 | viewModel.dismissButtonTouchUpInside() 44 | } 45 | 46 | @objc 47 | private func forgottenPasswordButtonTouchUpInside() { 48 | viewModel.forgottenPasswordButtonTouchUpInside() 49 | } 50 | 51 | @objc 52 | private func signUpButtonTouchUpInside() { 53 | viewModel.signUpButtonTouchUpInside() 54 | } 55 | 56 | deinit { 57 | print("♻️ \(self) Deallocated") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /RoutingExample/Authentication/LoginViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewModel.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class LoginViewModel { 12 | typealias Routes = LoginRoute & SignUpRoute & ForgottenPasswordRoute & Closable 13 | private var router: Routes 14 | 15 | init(router: Routes) { 16 | self.router = router 17 | } 18 | 19 | func dismissButtonTouchUpInside() { 20 | print("Dismiss Button pressed") 21 | router.close() 22 | } 23 | 24 | func forgottenPasswordButtonTouchUpInside() { 25 | print("Forgot Password Button pressed") 26 | router.openForgottenPassword() 27 | } 28 | 29 | func signUpButtonTouchUpInside() { 30 | print("Sign Up Button pressed") 31 | router.openSignUp() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RoutingExample/Authentication/SignUpViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SignUpViewController: UIViewController { 12 | private let viewModel: SignUpViewModel 13 | 14 | init(viewModel: SignUpViewModel) { 15 | self.viewModel = viewModel 16 | super.init(nibName: nil, bundle: nil) 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | view.backgroundColor = .white 26 | 27 | print("👶 \(self) Created") 28 | 29 | let dismissButton = DefaultButton(title: "Dismiss", target: self, selector: #selector(dismissButtonTouchUpInside)) 30 | let forgottenPasswordButton = DefaultButton(title: "Forgot Password", target: self, selector: #selector(forgottenPasswordButtonTouchUpInside)) 31 | 32 | let vStack = UIStackView(arrangedSubviews: [dismissButton, forgottenPasswordButton]) 33 | vStack.axis = .vertical 34 | vStack.spacing = 8.0 35 | 36 | view.addSubview(vStack) 37 | vStack.layout.center(in: view) 38 | } 39 | 40 | @objc 41 | private func forgottenPasswordButtonTouchUpInside() { 42 | print("Forgotten Password Button pressed") 43 | viewModel.forgottenPasswordButtonTouchUpInside() 44 | } 45 | 46 | @objc 47 | private func dismissButtonTouchUpInside() { 48 | print("Dismiss Button pressed") 49 | viewModel.dismissButtonTouchUpInside() 50 | } 51 | 52 | deinit { 53 | print("♻️ \(self) Deallocated") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /RoutingExample/Authentication/SignUpViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpViewModel.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class SignUpViewModel { 12 | typealias Routes = ForgottenPasswordRoute & Dismissable 13 | private var router: Routes 14 | 15 | init(router: Routes) { 16 | self.router = router 17 | } 18 | 19 | func forgottenPasswordButtonTouchUpInside() { 20 | print("Forgotten Password Button pressed") 21 | router.openForgottenPassword() 22 | } 23 | 24 | func dismissButtonTouchUpInside() { 25 | print("Dismiss Button pressed") 26 | router.dismiss() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RoutingExample/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 | -------------------------------------------------------------------------------- /RoutingExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleTypeRole 23 | Editor 24 | CFBundleURLName 25 | com.cassiuspacheco.RoutingExample 26 | CFBundleURLSchemes 27 | 28 | routerexample 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | LSRequiresIPhoneOS 35 | 36 | NSUserActivityTypes 37 | 38 | AddToWishlistIntent 39 | 40 | UIApplicationSceneManifest 41 | 42 | UIApplicationSupportsMultipleScenes 43 | 44 | UISceneConfigurations 45 | 46 | UIWindowSceneSessionRoleApplication 47 | 48 | 49 | UISceneConfigurationName 50 | Default Configuration 51 | UISceneDelegateClassName 52 | $(PRODUCT_MODULE_NAME).SceneDelegate 53 | 54 | 55 | 56 | 57 | UILaunchStoryboardName 58 | LaunchScreen 59 | UIRequiredDeviceCapabilities 60 | 61 | armv7 62 | 63 | UIStatusBarTintParameters 64 | 65 | UINavigationBar 66 | 67 | Style 68 | UIBarStyleDefault 69 | Translucent 70 | 71 | 72 | 73 | UISupportedInterfaceOrientations 74 | 75 | UIInterfaceOrientationPortrait 76 | UIInterfaceOrientationLandscapeLeft 77 | UIInterfaceOrientationLandscapeRight 78 | 79 | UISupportedInterfaceOrientations~ipad 80 | 81 | UIInterfaceOrientationPortrait 82 | UIInterfaceOrientationPortraitUpsideDown 83 | UIInterfaceOrientationLandscapeLeft 84 | UIInterfaceOrientationLandscapeRight 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /RoutingExample/PopUps/PopUpViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopUpViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class PopUpViewController: UIViewController { 12 | private let viewModel: PopUpViewModel 13 | 14 | init(viewModel: PopUpViewModel) { 15 | self.viewModel = viewModel 16 | super.init(nibName: nil, bundle: nil) 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | view.backgroundColor = .white 26 | 27 | print("👶 \(self) Created") 28 | 29 | let dismissButton = DefaultButton(title: "Dismiss", target: self, selector: #selector(dismissButtonTouchUpInside)) 30 | let label = UILabel() 31 | label.translatesAutoresizingMaskIntoConstraints = false 32 | label.text = viewModel.message 33 | 34 | let vStack = UIStackView(arrangedSubviews: [label, dismissButton]) 35 | vStack.axis = .vertical 36 | vStack.spacing = 8.0 37 | 38 | view.addSubview(vStack) 39 | vStack.layout.center(in: view) 40 | } 41 | 42 | @objc 43 | private func dismissButtonTouchUpInside() { 44 | viewModel.dismissButtonTouchUpInside() 45 | } 46 | 47 | deinit { 48 | print("♻️ \(self) Deallocated") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RoutingExample/PopUps/PopUpViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopUpViewModel.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class PopUpViewModel { 12 | typealias Routes = Closable 13 | private let router: Routes 14 | let message: String 15 | 16 | init(message: String, router: Routes) { 17 | self.message = message 18 | self.router = router 19 | } 20 | 21 | func dismissButtonTouchUpInside() { 22 | print("Dismiss Button pressed") 23 | router.close() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RoutingExample/Product/ProductViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IntentsUI 11 | 12 | final class ProductViewController: UIViewController { 13 | private let viewModel: ProductViewModel 14 | 15 | init(viewModel: ProductViewModel) { 16 | self.viewModel = viewModel 17 | super.init(nibName: nil, bundle: nil) 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | view.backgroundColor = .white 27 | 28 | print("👶 \(self) Created") 29 | 30 | let wishlistButton = DefaultButton(title: "Add to Wishlist", target: self, selector: #selector(wishlistButtonTouchUpInside)) 31 | let productButton = DefaultButton(title: "Open another Product", target: self, selector: #selector(productButtonTouchUpInside)) 32 | 33 | let vStack = UIStackView(arrangedSubviews: [wishlistButton, productButton]) 34 | vStack.axis = .vertical 35 | vStack.spacing = 8.0 36 | 37 | view.addSubview(vStack) 38 | vStack.layout.center(in: view) 39 | createAddToSiriButton() 40 | } 41 | 42 | private func createAddToSiriButton() { 43 | // Note that this is a useless intent. The idea is just to show the concept working. 44 | let intent = AddToWishlistIntent() 45 | intent.name = "Test" 46 | let shortcutButton = INUIAddVoiceShortcutButton(style: .whiteOutline) 47 | shortcutButton.shortcut = INShortcut(intent: intent) 48 | shortcutButton.delegate = viewModel.siriButtonDelegate 49 | 50 | navigationItem.setRightBarButton(UIBarButtonItem(customView: shortcutButton), animated: true) 51 | } 52 | 53 | @objc 54 | private func productButtonTouchUpInside() { 55 | viewModel.productButtonTouchUpInside() 56 | } 57 | 58 | @objc 59 | private func wishlistButtonTouchUpInside() { 60 | viewModel.wishlistButtonTouchUpInside() 61 | } 62 | 63 | deinit { 64 | print("♻️ \(self) Deallocated") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RoutingExample/Product/ProductViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductViewModel.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IntentsUI 11 | 12 | final class ProductViewModel { 13 | typealias Routes = ProductRoute & PopUpRoute & SiriRoute 14 | private let router: Routes 15 | 16 | // Uses the router as the delegate to handle the intent controller orchestration. 17 | var siriButtonDelegate: INUIAddVoiceShortcutButtonDelegate { 18 | return router 19 | } 20 | 21 | init(router: Routes) { 22 | self.router = router 23 | } 24 | 25 | func productButtonTouchUpInside() { 26 | print("Product Button pressed") 27 | router.openProduct() 28 | } 29 | 30 | func wishlistButtonTouchUpInside() { 31 | print("Wishlist Button pressed") 32 | router.openPopUp(withMessage: "Product added to the Wishlist!") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Animator/AnimatedTransitioning.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatedTransitioning.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol AnimatedTransitioning: UIViewControllerAnimatedTransitioning { 12 | var isPresenting: Bool { get set } 13 | } 14 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Animator/FadeAnimatedTransitioning.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FadeAnimator.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class FadeAnimatedTransitioning: NSObject, AnimatedTransitioning { 12 | var isPresenting: Bool = true 13 | 14 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 15 | return 0.35 16 | } 17 | 18 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 19 | if isPresenting { 20 | present(using: transitionContext) 21 | } else { 22 | dismiss(using: transitionContext) 23 | } 24 | } 25 | 26 | private func present(using transitionContext: UIViewControllerContextTransitioning) { 27 | guard let toView = transitionContext.view(forKey: .to) else { return } 28 | 29 | let containerView = transitionContext.containerView 30 | toView.alpha = 0.0 31 | containerView.addSubview(toView) 32 | 33 | UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { 34 | toView.alpha = 1.0 35 | }, completion: { _ in 36 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 37 | }) 38 | } 39 | 40 | private func dismiss(using transitionContext: UIViewControllerContextTransitioning) { 41 | guard let fromView = transitionContext.view(forKey: .from) else { return } 42 | 43 | UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { 44 | fromView.alpha = 0.0 45 | }, completion: { _ in 46 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Router.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 5/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol Closable: class { 12 | /// Closes the Router's root view controller using the transition used to show it. 13 | func close() 14 | 15 | /// Closes the Router's root view controller using the transition used to show it. 16 | func close(completion: (() -> Void)?) 17 | } 18 | 19 | protocol Dismissable: class { 20 | /// Dismisses the Router's root view controller ignoring the transition used to show it. 21 | func dismiss() 22 | 23 | /// Dismisses the Router's root view controller ignoring the transition used to show it. 24 | func dismiss(completion: (() -> Void)?) 25 | } 26 | 27 | protocol Deeplinkable: class { 28 | /// Route to a view controller applying the transition provided by resolving the URL to a specific Route. 29 | @discardableResult 30 | func route(to url: URL, as transition: Transition) -> Bool 31 | } 32 | 33 | protocol Routable: class { 34 | /// Route to a view controller using the transition provided. 35 | func route(to viewController: UIViewController, as transition: Transition) 36 | 37 | /// Route to a view controller using the transition provided. 38 | func route(to viewController: UIViewController, as transition: Transition, completion: (() -> Void)?) 39 | } 40 | 41 | protocol Router: Routable { 42 | /// The root view controller of this router. 43 | var root: UIViewController? { get set } 44 | 45 | // Depending on the App's architecture it may be better to expose 46 | // a way of accessing dependencies in the Routers through a DI Container, 47 | // or something similar, in order to ensure the controllers and 48 | // view models instantiated by this class become testable. 49 | // 50 | // Dependency Container example: 51 | // https://github.com/CassiusPacheco/DependencyContainer 52 | // 53 | // var container: DependencyContainer { get } 54 | } 55 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routers/DeeplinkRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeeplinkRouter.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DeeplinkRouter: DefaultRouter {} 12 | 13 | extension DeeplinkRouter: Deeplinkable { 14 | /// Route to a view controller using the URL and transition provided. 15 | /// The `url` passed in must match one of the specificed routes in the `AppRoutes` enum. 16 | @discardableResult 17 | func route(to url: URL, as transition: Transition) -> Bool { 18 | guard let route = AppRoutes(url: url) else { return false } 19 | 20 | switch route { 21 | case .product: 22 | openProduct(with: transition) 23 | case .login: 24 | openLogin(with: transition) 25 | case .signUp: 26 | openSignUp(with: transition) 27 | case .wishlist: 28 | selectWishlistTab() 29 | case .shop: 30 | selectShopTab() 31 | } 32 | 33 | return true 34 | } 35 | } 36 | 37 | enum AppRoutes: String { 38 | case product = "product" 39 | case login = "login" 40 | case signUp = "sign-up" 41 | case wishlist = "wishlist" 42 | case shop = "shop" 43 | 44 | init?(url: URL) { 45 | // Transforms urls such as `www.routing.com/product?something=meh` into `product` 46 | guard let routes = AppRoutes(rawValue: url.lastPathComponent) else { return nil} 47 | self = routes 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routers/DefaultRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultRouter.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DefaultRouter: NSObject, Router, Closable, Dismissable { 12 | private let rootTransition: Transition 13 | weak var root: UIViewController? 14 | 15 | init(rootTransition: Transition) { 16 | self.rootTransition = rootTransition 17 | } 18 | 19 | deinit { 20 | print("🗑 Deallocating \(self) with \(String(describing: rootTransition))") 21 | } 22 | 23 | // MARK: - Routable 24 | 25 | func route(to viewController: UIViewController, as transition: Transition, completion: (() -> Void)?) { 26 | guard let root = root else { return } 27 | transition.open(viewController, from: root, completion: completion) 28 | } 29 | 30 | func route(to viewController: UIViewController, as transition: Transition) { 31 | route(to: viewController, as: transition, completion: nil) 32 | } 33 | 34 | // MARK: - Closable 35 | 36 | func close(completion: (() -> Void)?) { 37 | guard let root = root else { return } 38 | // Removes the `root` with the same transition that it was opened. 39 | rootTransition.close(root, completion: completion) 40 | } 41 | 42 | func close() { 43 | close(completion: nil) 44 | } 45 | 46 | // MARK: - Dismissable 47 | 48 | func dismiss(completion: (() -> Void)?) { 49 | // Dismiss the root with iOS' default dismiss animation. 50 | // It will only work if the root or its ancestor were presented 51 | // using iOS' native present view controller method. 52 | root?.dismiss(animated: rootTransition.isAnimated, completion: completion) 53 | } 54 | 55 | func dismiss() { 56 | dismiss(completion: nil) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routers/SiriRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SiriRouter.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IntentsUI 11 | 12 | class SiriRouter: DefaultRouter {} 13 | 14 | extension SiriRouter: INUIAddVoiceShortcutButtonDelegate { 15 | func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) { 16 | addVoiceShortcutViewController.delegate = self 17 | root?.present(addVoiceShortcutViewController, animated: true, completion: nil) 18 | } 19 | 20 | func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) { 21 | editVoiceShortcutViewController.delegate = self 22 | root?.present(editVoiceShortcutViewController, animated: true, completion: nil) 23 | } 24 | } 25 | 26 | extension SiriRouter: INUIAddVoiceShortcutViewControllerDelegate { 27 | func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) { 28 | if let error = error as NSError? { 29 | print("Error adding voice shortcut: \(error)") 30 | } 31 | 32 | controller.dismiss(animated: true, completion: nil) 33 | } 34 | 35 | func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) { 36 | controller.dismiss(animated: true, completion: nil) 37 | } 38 | } 39 | 40 | extension SiriRouter: INUIEditVoiceShortcutViewControllerDelegate { 41 | func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) { 42 | if let error = error as NSError? { 43 | print("Error editing voice shortcut: \(error)") 44 | } 45 | 46 | controller.dismiss(animated: true, completion: nil) 47 | } 48 | 49 | func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) { 50 | controller.dismiss(animated: true, completion: nil) 51 | } 52 | 53 | func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) { 54 | controller.dismiss(animated: true, completion: nil) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/ForgottenPasswordRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForgottenPasswordRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ForgottenPasswordRoute { 12 | func openForgottenPassword() 13 | } 14 | 15 | extension ForgottenPasswordRoute where Self: Router { 16 | // This method isn't part of the interface and can only be used 17 | // internally by instances that conform to Router, like DefaultRouter, 18 | // DeeplinkRouter and others. 19 | func openForgottenPassword(with transition: Transition) { 20 | // If the `Router` makes use of a DI container it can resolve 21 | // the dependencies in a clean and testable way by doing something like: 22 | // 23 | // let viewController = container.resolve(ForgottenPasswordViewController.self, argument: router) 24 | 25 | let router = DefaultRouter(rootTransition: transition) 26 | let viewModel = ForgottenPasswordViewModel(router: router) 27 | let viewController = ForgottenPasswordViewController(viewModel: viewModel) 28 | router.root = viewController 29 | 30 | route(to: viewController, as: transition) 31 | } 32 | 33 | func openForgottenPassword() { 34 | openForgottenPassword(with: PushTransition()) 35 | } 36 | } 37 | 38 | extension DefaultRouter: ForgottenPasswordRoute {} 39 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/LoginRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 5/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol LoginRoute { 12 | func openLogin() 13 | } 14 | 15 | extension LoginRoute where Self: Router { 16 | // This method isn't part of the interface and can only be used 17 | // internally by instances that conform to Router, like DefaultRouter, 18 | // DeeplinkRouter and others. 19 | func openLogin(with transition: Transition) { 20 | // If the `Router` makes use of a DI container it can resolve 21 | // the dependencies in a clean and testable way by doing something like: 22 | // 23 | // let viewController = container.resolve(LoginViewController.self, argument: router) 24 | 25 | let router = DefaultRouter(rootTransition: transition) 26 | let viewModel = LoginViewModel(router: router) 27 | let viewController = LoginViewController(viewModel: viewModel) 28 | let navigationController = UINavigationController(rootViewController: viewController) 29 | router.root = viewController 30 | 31 | route(to: navigationController, as: transition) 32 | } 33 | 34 | func openLogin() { 35 | openLogin(with: ModalTransition()) 36 | } 37 | } 38 | 39 | extension DefaultRouter: LoginRoute {} 40 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/PopUpRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopUpRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol PopUpRoute { 12 | func openPopUp(withMessage message: String) 13 | } 14 | 15 | extension PopUpRoute where Self: Router { 16 | // This method isn't part of the interface and can only be used 17 | // internally by instances that conform to Router, like DefaultRouter, 18 | // DeeplinkRouter and others. 19 | func openPopUp(withMessage message: String, transition: Transition) { 20 | // If the `Router` makes use of a DI container it can resolve 21 | // the dependencies in a clean and testable way by doing something like: 22 | // 23 | // let viewController = container.resolve(PopUpViewController.self, argument: router) 24 | 25 | let router = DefaultRouter(rootTransition: transition) 26 | let viewModel = PopUpViewModel(message: message, router: router) 27 | let viewController = PopUpViewController(viewModel: viewModel) 28 | router.root = viewController 29 | 30 | route(to: viewController, as: transition) 31 | } 32 | 33 | func openPopUp(withMessage message: String) { 34 | let transition = AnimatedTransition(animatedTransition: FadeAnimatedTransitioning()) 35 | openPopUp(withMessage: message, transition: transition) 36 | } 37 | } 38 | 39 | extension DefaultRouter: PopUpRoute {} 40 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/ProductRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ProductRoute { 12 | func openProduct() 13 | } 14 | 15 | extension ProductRoute where Self: Router { 16 | // This method isn't part of the interface and can only be used 17 | // internally by instances that conform to Router, like DefaultRouter, 18 | // DeeplinkRouter and others. 19 | func openProduct(with transition: Transition) { 20 | // If the `Router` makes use of a DI container it can resolve 21 | // the dependencies in a clean and testable way by doing something like: 22 | // 23 | // let viewController = container.resolve(ProductViewController.self, argument: router) 24 | 25 | let router = SiriRouter(rootTransition: transition) 26 | let viewModel = ProductViewModel(router: router) 27 | let viewController = ProductViewController(viewModel: viewModel) 28 | router.root = viewController 29 | 30 | route(to: viewController, as: transition) 31 | } 32 | 33 | func openProduct() { 34 | openProduct(with: PushTransition()) 35 | } 36 | } 37 | 38 | extension DefaultRouter: ProductRoute {} 39 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/SignUpRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 5/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol SignUpRoute { 12 | func openSignUp() 13 | } 14 | 15 | extension SignUpRoute where Self: Router { 16 | // This method isn't part of the interface and can only be used 17 | // internally by instances that conform to Router, like DefaultRouter, 18 | // DeeplinkRouter and others. 19 | func openSignUp(with transition: Transition) { 20 | // If the `Router` makes use of a DI container it can resolve 21 | // the dependencies in a clean and testable way by doing something like: 22 | // 23 | // let viewController = container.resolve(SignUpViewController.self, argument: router) 24 | 25 | let router = DefaultRouter(rootTransition: transition) 26 | let viewModel = SignUpViewModel(router: router) 27 | let viewController = SignUpViewController(viewModel: viewModel) 28 | router.root = viewController 29 | 30 | route(to: viewController, as: transition) 31 | } 32 | 33 | func openSignUp() { 34 | openSignUp(with: PushTransition()) 35 | } 36 | } 37 | 38 | extension DefaultRouter: SignUpRoute {} 39 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/SiriRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SiriRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IntentsUI 11 | 12 | protocol SiriRoute: INUIAddVoiceShortcutButtonDelegate {} 13 | 14 | extension SiriRouter: SiriRoute {} 15 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/Tabs/ShopTabRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShopTabRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 11/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ShopTabRoute { 12 | func makeShopTab() -> UIViewController 13 | } 14 | 15 | extension ShopTabRoute where Self: Router { 16 | func makeShopTab() -> UIViewController { 17 | // No transitions since these are managed by the TabBarController 18 | let router = DefaultRouter(rootTransition: EmptyTransition()) 19 | let viewModel = ShopViewModel(router: router) 20 | let viewControlle = ShopViewController(viewModel: viewModel) 21 | router.root = viewControlle 22 | 23 | let navigation = UINavigationController(rootViewController: viewControlle) 24 | navigation.tabBarItem = Tabs.shop.item 25 | return navigation 26 | } 27 | 28 | // This method isn't part of the interface and can only be used 29 | // internally by instances that conform to Router, like DefaultRouter, 30 | // DeeplinkRouter and others. 31 | func selectShopTab() { 32 | root?.tabBarController?.selectedIndex = Tabs.shop.index 33 | } 34 | } 35 | 36 | extension DefaultRouter: ShopTabRoute {} 37 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Routes/Tabs/WishlistTabRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishlistTabRoute.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 11/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol WishlistTabRoute { 12 | func makeWishlistTab() -> UIViewController 13 | } 14 | 15 | extension WishlistTabRoute where Self: Router { 16 | func makeWishlistTab() -> UIViewController { 17 | // No transitions since these are managed by the TabBarController 18 | let router = DefaultRouter(rootTransition: EmptyTransition()) 19 | let viewModel = WishlistViewModel(router: router) 20 | let viewController = WishlistViewController(viewModel: viewModel) 21 | router.root = viewController 22 | 23 | let navigation = UINavigationController(rootViewController: viewController) 24 | navigation.tabBarItem = Tabs.wishlist.item 25 | return navigation 26 | } 27 | 28 | // This method isn't part of the interface and can only be used 29 | // internally by instances that conform to Router, like DefaultRouter, 30 | // DeeplinkRouter and others. 31 | func selectWishlistTab() { 32 | root?.tabBarController?.selectedIndex = Tabs.wishlist.index 33 | } 34 | } 35 | 36 | extension DefaultRouter: WishlistTabRoute {} 37 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Transitions/AnimatedTransition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatorTransition.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class AnimatedTransition: NSObject { 12 | let animatedTransition: AnimatedTransitioning 13 | var isAnimated: Bool = true 14 | 15 | init(animatedTransition: AnimatedTransitioning, isAnimated: Bool = true) { 16 | self.animatedTransition = animatedTransition 17 | self.isAnimated = isAnimated 18 | } 19 | } 20 | 21 | extension AnimatedTransition: Transition { 22 | // MARK: - Transition 23 | 24 | func open(_ viewController: UIViewController, from: UIViewController, completion: (() -> Void)?) { 25 | viewController.transitioningDelegate = self 26 | viewController.modalPresentationStyle = .custom 27 | from.present(viewController, animated: isAnimated, completion: completion) 28 | } 29 | 30 | func close(_ viewController: UIViewController, completion: (() -> Void)?) { 31 | viewController.dismiss(animated: isAnimated, completion: completion) 32 | } 33 | } 34 | 35 | extension AnimatedTransition: UIViewControllerTransitioningDelegate { 36 | func animationController(forPresented presented: UIViewController, 37 | presenting: UIViewController, 38 | source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 39 | animatedTransition.isPresenting = true 40 | return animatedTransition 41 | } 42 | 43 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 44 | animatedTransition.isPresenting = false 45 | return animatedTransition 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Transitions/EmptyTransition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyTransition.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class EmptyTransition { 12 | var isAnimated: Bool = true 13 | } 14 | 15 | extension EmptyTransition: Transition { 16 | // MARK: - Transition 17 | 18 | func open(_ viewController: UIViewController, from: UIViewController, completion: (() -> Void)?) {} 19 | func close(_ viewController: UIViewController, completion: (() -> Void)?) {} 20 | } 21 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Transitions/ModalTransition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModalTransition.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 5/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ModalTransition: NSObject { 12 | var isAnimated: Bool = true 13 | 14 | var modalTransitionStyle: UIModalTransitionStyle 15 | var modalPresentationStyle: UIModalPresentationStyle 16 | 17 | init(isAnimated: Bool = true, 18 | modalTransitionStyle: UIModalTransitionStyle = .coverVertical, 19 | modalPresentationStyle: UIModalPresentationStyle = .automatic) { 20 | self.isAnimated = isAnimated 21 | self.modalTransitionStyle = modalTransitionStyle 22 | self.modalPresentationStyle = modalPresentationStyle 23 | } 24 | } 25 | 26 | extension ModalTransition: Transition { 27 | // MARK: - Transition 28 | 29 | func open(_ viewController: UIViewController, from: UIViewController, completion: (() -> Void)?) { 30 | viewController.modalPresentationStyle = modalPresentationStyle 31 | viewController.modalTransitionStyle = modalTransitionStyle 32 | from.present(viewController, animated: isAnimated, completion: completion) 33 | } 34 | 35 | func close(_ viewController: UIViewController, completion: (() -> Void)?) { 36 | viewController.dismiss(animated: isAnimated, completion: completion) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Transitions/PushTransition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushTransition.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 5/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class PushTransition: NSObject { 12 | var isAnimated: Bool = true 13 | 14 | private weak var from: UIViewController? 15 | private var openCompletionHandler: (() -> Void)? 16 | private var closeCompletionHandler: (() -> Void)? 17 | 18 | private var navigationController: UINavigationController? { 19 | guard let navigation = from as? UINavigationController else { return from?.navigationController } 20 | return navigation 21 | } 22 | 23 | init(isAnimated: Bool = true) { 24 | self.isAnimated = isAnimated 25 | } 26 | } 27 | 28 | extension PushTransition: Transition { 29 | // MARK: - Transition 30 | 31 | func open(_ viewController: UIViewController, from: UIViewController, completion: (() -> Void)?) { 32 | self.from = from 33 | openCompletionHandler = completion 34 | navigationController?.delegate = self 35 | navigationController?.pushViewController(viewController, animated: isAnimated) 36 | } 37 | 38 | func close(_ viewController: UIViewController, completion: (() -> Void)?) { 39 | closeCompletionHandler = completion 40 | navigationController?.popViewController(animated: isAnimated) 41 | } 42 | } 43 | 44 | extension PushTransition: UINavigationControllerDelegate { 45 | // MARK: - UINavigationControllerDelegate 46 | 47 | func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { 48 | guard let transitionCoordinator = navigationController.transitionCoordinator, 49 | let fromVC = transitionCoordinator.viewController(forKey: .from), 50 | let toVC = transitionCoordinator.viewController(forKey: .to) else { return } 51 | 52 | if fromVC == from { 53 | openCompletionHandler?() 54 | openCompletionHandler = nil 55 | } else if toVC == from { 56 | closeCompletionHandler?() 57 | closeCompletionHandler = nil 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RoutingExample/Routing/Transitions/Transition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transition.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 5/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol Transition: class { 12 | var isAnimated: Bool { get set } 13 | 14 | func open(_ viewController: UIViewController, from: UIViewController, completion: (() -> Void)?) 15 | func close(_ viewController: UIViewController, completion: (() -> Void)?) 16 | } 17 | -------------------------------------------------------------------------------- /RoutingExample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | private var deeplinkRouter: Router? 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | 18 | let mainRouter = DefaultRouter(rootTransition: EmptyTransition()) 19 | let tabs = [mainRouter.makeShopTab(), mainRouter.makeWishlistTab()] 20 | 21 | window = UIWindow(frame: windowScene.coordinateSpace.bounds) 22 | window?.windowScene = windowScene 23 | window?.rootViewController = MainTabBarController(viewControllers: tabs) 24 | window?.makeKeyAndVisible() 25 | 26 | if let urlContext = connectionOptions.urlContexts.first { 27 | deeplink(urlContext.url) 28 | } 29 | } 30 | 31 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { 32 | guard let urlContext = URLContexts.first else { return } 33 | deeplink(urlContext.url) 34 | } 35 | 36 | private func deeplink(_ url: URL) { 37 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 38 | // Define all deeplinks to be opened with a Modal Transition 39 | let transition = ModalTransition() 40 | let router = DeeplinkRouter(rootTransition: transition) 41 | router.root = self.window?.topMostViewController() 42 | router.route(to: url, as: transition) 43 | self.deeplinkRouter = router 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /RoutingExample/Tabs/MainTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum Tabs { 12 | case shop 13 | case wishlist 14 | 15 | var index: Int { 16 | switch self { 17 | case .shop: 18 | return 0 19 | case .wishlist: 20 | return 1 21 | } 22 | } 23 | 24 | var item: UITabBarItem { 25 | switch self { 26 | case .shop: 27 | return UITabBarItem(title: "Shop", image: nil, tag: index) 28 | case .wishlist: 29 | return UITabBarItem(title: "Wishlist", image: nil, tag: index) 30 | } 31 | } 32 | } 33 | 34 | final class MainTabBarController: UITabBarController { 35 | required init(viewControllers: [UIViewController]) { 36 | super.init(nibName: nil, bundle: nil) 37 | self.viewControllers = viewControllers 38 | } 39 | 40 | required init?(coder: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | view.backgroundColor = .white 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RoutingExample/Tabs/ShopViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShopViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ShopViewController: UIViewController { 12 | private let viewModel: ShopViewModel 13 | 14 | init(viewModel: ShopViewModel) { 15 | self.viewModel = viewModel 16 | super.init(nibName: nil, bundle: nil) 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | view.backgroundColor = .white 26 | title = "Shop" 27 | 28 | let productButton = DefaultButton(title: "Open a Product", target: self, selector: #selector(productButtonTouchUpInside)) 29 | view.addSubview(productButton) 30 | productButton.layout.center(in: view) 31 | } 32 | 33 | @objc 34 | private func productButtonTouchUpInside() { 35 | viewModel.productButtonTouchUpInside() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /RoutingExample/Tabs/ShopViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShopViewModel.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class ShopViewModel { 12 | typealias Routes = ProductRoute 13 | private let router: Routes 14 | 15 | init(router: Routes) { 16 | self.router = router 17 | } 18 | 19 | func productButtonTouchUpInside() { 20 | print("Product Button pressed") 21 | router.openProduct() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RoutingExample/Tabs/WishlistViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishlistViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class WishlistViewController: UIViewController { 12 | private let viewModel: WishlistViewModel 13 | 14 | init(viewModel: WishlistViewModel) { 15 | self.viewModel = viewModel 16 | super.init(nibName: nil, bundle: nil) 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | view.backgroundColor = .white 26 | title = "Wishlist" 27 | 28 | let loginButton = DefaultButton(title: "Login", target: self, selector: #selector(loginButtonTouchUpInside)) 29 | let productButton = DefaultButton(title: "Open a Product", target: self, selector: #selector(productButtonTouchUpInside)) 30 | 31 | let vStack = UIStackView(arrangedSubviews: [loginButton, productButton]) 32 | vStack.axis = .vertical 33 | vStack.spacing = 8.0 34 | 35 | view.addSubview(vStack) 36 | vStack.layout.center(in: view) 37 | } 38 | 39 | @objc 40 | private func productButtonTouchUpInside() { 41 | viewModel.productButtonTouchUpInside() 42 | } 43 | 44 | @objc 45 | private func loginButtonTouchUpInside() { 46 | viewModel.loginButtonTouchUpInside() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RoutingExample/Tabs/WishlistViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishlistViewModel.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class WishlistViewModel { 12 | typealias Routes = LoginRoute & ProductRoute 13 | private let router: Routes 14 | 15 | init(router: Routes) { 16 | self.router = router 17 | } 18 | 19 | func productButtonTouchUpInside() { 20 | print("Product Button pressed") 21 | router.openProduct() 22 | } 23 | 24 | func loginButtonTouchUpInside() { 25 | print("Login Button pressed") 26 | router.openLogin() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RoutingExample/Utils/DefaultButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DefaultButton: UIButton { 12 | required init(title: String, target: Any, selector: Selector) { 13 | super.init(frame: .zero) 14 | translatesAutoresizingMaskIntoConstraints = false 15 | setTitle(title, for: .normal) 16 | setTitleColor(.systemBlue, for: .normal) 17 | addTarget(target, action: selector, for: .touchUpInside) 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RoutingExample/Utils/LayoutHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutHelper.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | // From: https://gist.github.com/CassiusPacheco/17ac1af2f7c1fb12c48ee0c252980468 13 | public func associatedObject(base: AnyObject, key: UnsafePointer, initializer: () -> ValueType) 14 | -> ValueType { 15 | // if there is already an associated value returns it 16 | if let associated = objc_getAssociatedObject(base, key) as? ValueType { 17 | return associated 18 | } 19 | 20 | // associated value not found, initializes closure, makes the association and returns it 21 | let associated = initializer() 22 | associateObject(base: base, key: key, value: associated) 23 | 24 | return associated 25 | } 26 | 27 | public func associateObject(base: AnyObject, key: UnsafePointer, value: ValueType) { 28 | objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 29 | } 30 | 31 | // Add a `layout` property to all UIViews 32 | private struct AssociatedKeys { 33 | static var layout: UInt8 = 0 34 | } 35 | 36 | extension UIView { 37 | // MARK: - Autolayout Helper 38 | var layout: LayoutHelper { 39 | return associatedObject(base: self, key: &AssociatedKeys.layout, initializer: { () -> LayoutHelper in 40 | return LayoutHelper(view: self) 41 | }) 42 | } 43 | } 44 | 45 | // LayoutHelper adds a bit of sugar syntax to NSLayoutConstraints 46 | struct LayoutHelper { 47 | private weak var view: UIView? 48 | 49 | init(view: UIView) { 50 | self.view = view 51 | } 52 | 53 | typealias PinConstraints = (leading: NSLayoutConstraint?, trailing: NSLayoutConstraint?, top: NSLayoutConstraint?, bottom: NSLayoutConstraint?) 54 | @discardableResult 55 | func pin(to outerView: UIView, edgeInsets: UIEdgeInsets = .zero, priority: UILayoutPriority = .required) -> PinConstraints { 56 | guard let view = self.view else { 57 | assertionFailure("View cannot be nil") 58 | return (nil, nil, nil, nil) 59 | } 60 | 61 | view.translatesAutoresizingMaskIntoConstraints = false 62 | 63 | let leading = view.leadingAnchor.constraint(equalTo: outerView.leadingAnchor, constant: edgeInsets.left) 64 | let trailing = view.trailingAnchor.constraint(equalTo: outerView.trailingAnchor, constant: -edgeInsets.right) 65 | let top = view.topAnchor.constraint(equalTo: outerView.topAnchor, constant: edgeInsets.top) 66 | let bottom = view.bottomAnchor.constraint(equalTo: outerView.bottomAnchor, constant: -edgeInsets.bottom) 67 | 68 | let constraints = [leading, trailing, top, bottom] 69 | constraints.forEach { $0.priority = priority } 70 | NSLayoutConstraint.activate(constraints) 71 | 72 | return (leading, trailing, top, bottom) 73 | } 74 | 75 | @discardableResult 76 | func center(in otherView: UIView, offset: CGPoint = .zero, priority: UILayoutPriority = .required) -> (centerX: NSLayoutConstraint?, centerY: NSLayoutConstraint?) { 77 | guard let view = self.view else { 78 | assertionFailure("View cannot be nil") 79 | return (nil, nil) 80 | } 81 | 82 | view.translatesAutoresizingMaskIntoConstraints = false 83 | 84 | let centerX = view.centerXAnchor.constraint(equalTo: otherView.centerXAnchor, constant: offset.x) 85 | let centerY = view.centerYAnchor.constraint(equalTo: otherView.centerYAnchor, constant: offset.y) 86 | 87 | let constraints = [centerX, centerY] 88 | constraints.forEach { $0.priority = priority } 89 | NSLayoutConstraint.activate(constraints) 90 | 91 | return (centerX, centerY) 92 | } 93 | 94 | @discardableResult 95 | func constrain(size: CGSize, priority: UILayoutPriority = .required) -> (width: NSLayoutConstraint?, height: NSLayoutConstraint?) { 96 | return constrain(width: size.width, height: size.height, priority: priority) 97 | } 98 | 99 | @discardableResult 100 | func constrain(width: CGFloat? = nil, height: CGFloat? = nil, priority: UILayoutPriority = .required) -> (width: NSLayoutConstraint?, height: NSLayoutConstraint?) { 101 | guard let view = self.view else { 102 | assertionFailure("View cannot be nil") 103 | return (nil, nil) 104 | } 105 | 106 | view.translatesAutoresizingMaskIntoConstraints = false 107 | 108 | var widthConstraint: NSLayoutConstraint? 109 | if let width = width { 110 | widthConstraint = view.widthAnchor.constraint(equalToConstant: width) 111 | widthConstraint?.priority = priority 112 | widthConstraint?.isActive = true 113 | } 114 | 115 | var heightConstraint: NSLayoutConstraint? 116 | if let height = height { 117 | heightConstraint = view.heightAnchor.constraint(equalToConstant: height) 118 | heightConstraint?.priority = priority 119 | heightConstraint?.isActive = true 120 | } 121 | 122 | return (widthConstraint, heightConstraint) 123 | } 124 | 125 | enum AnchorWithOffset { 126 | case top(_ offset: CGFloat = 0) 127 | case bottom(_ offset: CGFloat = 0) 128 | case leading(_ offset: CGFloat = 0) 129 | case trailing(_ offset: CGFloat = 0) 130 | case centerY(_ offset: CGFloat = 0) 131 | case centerX(_ offset: CGFloat = 0) 132 | case height(_ offset: CGFloat = 0) 133 | case width(_ offset: CGFloat = 0) 134 | } 135 | 136 | @discardableResult 137 | func constrain(anchors: [AnchorWithOffset], equalTo otherView: UIView, priority: UILayoutPriority = .required) -> [NSLayoutConstraint] { 138 | guard let view = self.view else { 139 | assertionFailure("View cannot be nil") 140 | return [] 141 | } 142 | view.translatesAutoresizingMaskIntoConstraints = false 143 | var constraints: [NSLayoutConstraint] = [] 144 | 145 | for anchor in anchors { 146 | switch anchor { 147 | case let .top(offset): 148 | constraints.append(view.topAnchor.constraint(equalTo: otherView.topAnchor, constant: offset)) 149 | case let .bottom(offset): 150 | constraints.append(view.bottomAnchor.constraint(equalTo: otherView.bottomAnchor, constant: offset)) 151 | case let .leading(offset): 152 | constraints.append(view.leadingAnchor.constraint(equalTo: otherView.leadingAnchor, constant: offset)) 153 | case let .trailing(offset): 154 | constraints.append(view.trailingAnchor.constraint(equalTo: otherView.trailingAnchor, constant: offset)) 155 | case let .centerY(offset): 156 | constraints.append(view.centerYAnchor.constraint(equalTo: otherView.centerYAnchor, constant: offset)) 157 | case let .centerX(offset): 158 | constraints.append(view.centerXAnchor.constraint(equalTo: otherView.centerXAnchor, constant: offset)) 159 | case let .height(offset): 160 | constraints.append(view.heightAnchor.constraint(equalTo: otherView.heightAnchor, constant: offset)) 161 | case let .width(offset): 162 | constraints.append(view.widthAnchor.constraint(equalTo: otherView.widthAnchor, constant: offset)) 163 | } 164 | } 165 | 166 | constraints.forEach { $0.priority = priority } 167 | NSLayoutConstraint.activate(constraints) 168 | 169 | return constraints 170 | } 171 | 172 | @discardableResult 173 | func constrain(anchors: [AnchorWithOffset], greaterThanOrEqualTo otherView: UIView, priority: UILayoutPriority = .required) -> [NSLayoutConstraint] { 174 | guard let view = self.view else { 175 | assertionFailure("View cannot be nil") 176 | return [] 177 | } 178 | 179 | view.translatesAutoresizingMaskIntoConstraints = false 180 | var constraints: [NSLayoutConstraint] = [] 181 | 182 | for anchor in anchors { 183 | switch anchor { 184 | case let .top(offset): 185 | constraints.append(view.topAnchor.constraint(greaterThanOrEqualTo: otherView.topAnchor, constant: offset)) 186 | case let .bottom(offset): 187 | constraints.append(view.bottomAnchor.constraint(greaterThanOrEqualTo: otherView.bottomAnchor, constant: offset)) 188 | case let .leading(offset): 189 | constraints.append(view.leadingAnchor.constraint(greaterThanOrEqualTo: otherView.leadingAnchor, constant: offset)) 190 | case let .trailing(offset): 191 | constraints.append(view.trailingAnchor.constraint(greaterThanOrEqualTo: otherView.trailingAnchor, constant: offset)) 192 | case let .centerY(offset): 193 | constraints.append(view.centerYAnchor.constraint(greaterThanOrEqualTo: otherView.centerYAnchor, constant: offset)) 194 | case let .centerX(offset): 195 | constraints.append(view.centerXAnchor.constraint(greaterThanOrEqualTo: otherView.centerXAnchor, constant: offset)) 196 | case let .height(offset): 197 | constraints.append(view.heightAnchor.constraint(greaterThanOrEqualTo: otherView.heightAnchor, constant: offset)) 198 | case let .width(offset): 199 | constraints.append(view.widthAnchor.constraint(greaterThanOrEqualTo: otherView.widthAnchor, constant: offset)) 200 | } 201 | } 202 | 203 | constraints.forEach { $0.priority = priority } 204 | NSLayoutConstraint.activate(constraints) 205 | 206 | return constraints 207 | } 208 | 209 | @discardableResult 210 | func constrain(anchors: [AnchorWithOffset], lessThanOrEqualTo otherView: UIView, priority: UILayoutPriority = .required) -> [NSLayoutConstraint] { 211 | guard let view = self.view else { 212 | assertionFailure("View cannot be nil") 213 | return [] 214 | } 215 | 216 | view.translatesAutoresizingMaskIntoConstraints = false 217 | var constraints: [NSLayoutConstraint] = [] 218 | 219 | for anchor in anchors { 220 | switch anchor { 221 | case let .top(offset): 222 | constraints.append(view.topAnchor.constraint(lessThanOrEqualTo: otherView.topAnchor, constant: offset)) 223 | case let .bottom(offset): 224 | constraints.append(view.bottomAnchor.constraint(lessThanOrEqualTo: otherView.bottomAnchor, constant: offset)) 225 | case let .leading(offset): 226 | constraints.append(view.leadingAnchor.constraint(lessThanOrEqualTo: otherView.leadingAnchor, constant: offset)) 227 | case let .trailing(offset): 228 | constraints.append(view.trailingAnchor.constraint(lessThanOrEqualTo: otherView.trailingAnchor, constant: offset)) 229 | case let .centerY(offset): 230 | constraints.append(view.centerYAnchor.constraint(lessThanOrEqualTo: otherView.centerYAnchor, constant: offset)) 231 | case let .centerX(offset): 232 | constraints.append(view.centerXAnchor.constraint(lessThanOrEqualTo: otherView.centerXAnchor, constant: offset)) 233 | case let .height(offset): 234 | constraints.append(view.heightAnchor.constraint(lessThanOrEqualTo: otherView.heightAnchor, constant: offset)) 235 | case let .width(offset): 236 | constraints.append(view.widthAnchor.constraint(lessThanOrEqualTo: otherView.widthAnchor, constant: offset)) 237 | } 238 | } 239 | 240 | constraints.forEach { $0.priority = priority } 241 | NSLayoutConstraint.activate(constraints) 242 | 243 | return constraints 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /RoutingExample/Utils/UIViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController.swift 3 | // RoutingExample 4 | // 5 | // Created by Cassius Pacheco on 8/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // From: https://gist.github.com/CassiusPacheco/a3887e5da50ec2f5fe0e7e9d072a4937 12 | extension UIViewController { 13 | /// Goes recursively through the `window`'s `rootViewController` hierarchy to return the top most controller. 14 | /// This method takes care of multiple presentation stacks being used by different view controllers, navigation controllers 15 | /// and tab bar controllers. 16 | /// May return `nil` if the window's `rootViewController` is not set. For all the other cases it should return a 17 | /// valid controller as long as there's something visible to the users. 18 | @objc 19 | func topMostViewController(for window: UIWindow? = UIApplication.shared.windows.first) -> UIViewController? { 20 | guard let rootViewController = window?.rootViewController else { 21 | assertionFailure("Window should always be available") 22 | return nil 23 | } 24 | 25 | var top: UIViewController? = rootViewController 26 | 27 | while true { 28 | if let presented = top?.presentedViewController { 29 | top = presented 30 | } else if let nav = top as? UINavigationController { 31 | top = nav.visibleViewController 32 | } else if let tab = top as? UITabBarController { 33 | top = tab.selectedViewController 34 | } else { 35 | break 36 | } 37 | } 38 | 39 | return top 40 | } 41 | } 42 | 43 | extension UIWindow { 44 | /// Goes recursively through the `window`'s `rootViewController` hierarchy to return the top most controller. 45 | /// This method takes care of multiple presentation stacks being used by different view controllers, navigation controllers 46 | /// and tab bar controllers. 47 | /// May return `nil` if the window's `rootViewController` is not set. For all the other cases it should return a 48 | /// valid controller as long as there's something visible to the users. 49 | func topMostViewController() -> UIViewController? { 50 | return rootViewController?.topMostViewController(for: self) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RoutingExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /RoutingExampleTests/RoutingExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoutingExampleTests.swift 3 | // RoutingExampleTests 4 | // 5 | // Created by Cassius Pacheco on 7/3/20. 6 | // Copyright © 2020 Cassius Pacheco. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import RoutingExample 11 | 12 | class RoutingExampleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | --------------------------------------------------------------------------------