├── .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 | 
8 |
9 | ## App's Flow Gif
10 |
11 | 
12 |
13 | ## Deeplinking Example
14 |
15 | 
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 |
--------------------------------------------------------------------------------