├── LICENSE ├── NavigationDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── azatzulkarnyaev.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist └── xcuserdata │ └── azatzulkarnyaev.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── NavigationDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── DemoImplementations │ ├── DemoViewControllerContextTransitionProvider.swift │ ├── DemoViewControllersByContextFactory.swift │ ├── ModalTransition.swift │ ├── NavigationControllerTransition.swift │ ├── NavigationStackChecker.swift │ └── TabBarContextSwitcher.swift ├── Info.plist ├── LinkParser.swift ├── Navigator │ ├── TopViewControllerProvider.swift │ ├── ViewControllerContext.swift │ ├── ViewControllerContextHolder.swift │ ├── ViewControllerContextRouter.swift │ ├── ViewControllerContextSwitcher.swift │ ├── ViewControllerContextTransition.swift │ ├── ViewControllerContextTransitionProvider.swift │ └── ViewControllersByContextFactory.swift ├── Screens │ ├── ChatViewController.swift │ ├── ContactsViewController.swift │ ├── ProfileViewController.swift │ └── ScreenType.swift ├── TabBarFactory.swift └── UIKitExtensions │ ├── UINavigationController+TopViewControllerProvider.swift │ └── UITabBarController+TopViewControllerProvider.swift └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 azatZul 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 | -------------------------------------------------------------------------------- /NavigationDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D65A3FC023832D1400DEBF49 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FBF23832D1400DEBF49 /* AppDelegate.swift */; }; 11 | D65A3FC923832D1600DEBF49 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D65A3FC823832D1600DEBF49 /* Assets.xcassets */; }; 12 | D65A3FCC23832D1600DEBF49 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D65A3FCA23832D1600DEBF49 /* LaunchScreen.storyboard */; }; 13 | D65A3FD523832D3B00DEBF49 /* ViewControllerContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FD423832D3B00DEBF49 /* ViewControllerContext.swift */; }; 14 | D65A3FD723832D5300DEBF49 /* ViewControllerContextHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FD623832D5300DEBF49 /* ViewControllerContextHolder.swift */; }; 15 | D65A3FD923832D6A00DEBF49 /* ViewControllerContextRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FD823832D6A00DEBF49 /* ViewControllerContextRouter.swift */; }; 16 | D65A3FDB23832DA100DEBF49 /* ViewControllersByContextFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FDA23832DA100DEBF49 /* ViewControllersByContextFactory.swift */; }; 17 | D65A3FDD23832DBB00DEBF49 /* ViewControllerContextTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FDC23832DBB00DEBF49 /* ViewControllerContextTransition.swift */; }; 18 | D65A3FDF23832DD700DEBF49 /* ViewControllerContextTransitionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FDE23832DD700DEBF49 /* ViewControllerContextTransitionProvider.swift */; }; 19 | D65A3FE123832DFF00DEBF49 /* ViewControllerContextSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FE023832DFF00DEBF49 /* ViewControllerContextSwitcher.swift */; }; 20 | D65A3FE32383339E00DEBF49 /* TabBarFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FE22383339E00DEBF49 /* TabBarFactory.swift */; }; 21 | D65A3FE5238334DA00DEBF49 /* ScreenType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FE4238334DA00DEBF49 /* ScreenType.swift */; }; 22 | D65A3FE72383360F00DEBF49 /* TabBarContextSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FE62383360F00DEBF49 /* TabBarContextSwitcher.swift */; }; 23 | D65A3FEA2383364E00DEBF49 /* ModalTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FE92383364E00DEBF49 /* ModalTransition.swift */; }; 24 | D65A3FEC2383369D00DEBF49 /* NavigationControllerTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FEB2383369D00DEBF49 /* NavigationControllerTransition.swift */; }; 25 | D65A3FEF2383423600DEBF49 /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FEE2383423600DEBF49 /* ContactsViewController.swift */; }; 26 | D65A3FF12383435400DEBF49 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FF02383435400DEBF49 /* ProfileViewController.swift */; }; 27 | D65A3FF32383455B00DEBF49 /* DemoViewControllersByContextFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FF22383455B00DEBF49 /* DemoViewControllersByContextFactory.swift */; }; 28 | D65A3FF5238345AE00DEBF49 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FF4238345AE00DEBF49 /* ChatViewController.swift */; }; 29 | D65A3FF72383479D00DEBF49 /* DemoViewControllerContextTransitionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FF62383479D00DEBF49 /* DemoViewControllerContextTransitionProvider.swift */; }; 30 | D65A3FFC2383490200DEBF49 /* TopViewControllerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FFB2383490200DEBF49 /* TopViewControllerProvider.swift */; }; 31 | D65A3FFE2383494100DEBF49 /* UITabBarController+TopViewControllerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FFD2383494100DEBF49 /* UITabBarController+TopViewControllerProvider.swift */; }; 32 | D65A400023834B1C00DEBF49 /* UINavigationController+TopViewControllerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A3FFF23834B1C00DEBF49 /* UINavigationController+TopViewControllerProvider.swift */; }; 33 | D65A4449238401AF00DEBF49 /* NavigationStackChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A4448238401AF00DEBF49 /* NavigationStackChecker.swift */; }; 34 | D65A4E642384117400DEBF49 /* LinkParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65A4E632384117400DEBF49 /* LinkParser.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | D65A3FBC23832D1400DEBF49 /* NavigationDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NavigationDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | D65A3FBF23832D1400DEBF49 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | D65A3FC823832D1600DEBF49 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | D65A3FCB23832D1600DEBF49 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 42 | D65A3FCD23832D1600DEBF49 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | D65A3FD423832D3B00DEBF49 /* ViewControllerContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerContext.swift; sourceTree = ""; }; 44 | D65A3FD623832D5300DEBF49 /* ViewControllerContextHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerContextHolder.swift; sourceTree = ""; }; 45 | D65A3FD823832D6A00DEBF49 /* ViewControllerContextRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerContextRouter.swift; sourceTree = ""; }; 46 | D65A3FDA23832DA100DEBF49 /* ViewControllersByContextFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllersByContextFactory.swift; sourceTree = ""; }; 47 | D65A3FDC23832DBB00DEBF49 /* ViewControllerContextTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerContextTransition.swift; sourceTree = ""; }; 48 | D65A3FDE23832DD700DEBF49 /* ViewControllerContextTransitionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerContextTransitionProvider.swift; sourceTree = ""; }; 49 | D65A3FE023832DFF00DEBF49 /* ViewControllerContextSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerContextSwitcher.swift; sourceTree = ""; }; 50 | D65A3FE22383339E00DEBF49 /* TabBarFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarFactory.swift; sourceTree = ""; }; 51 | D65A3FE4238334DA00DEBF49 /* ScreenType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenType.swift; sourceTree = ""; }; 52 | D65A3FE62383360F00DEBF49 /* TabBarContextSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarContextSwitcher.swift; sourceTree = ""; }; 53 | D65A3FE92383364E00DEBF49 /* ModalTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalTransition.swift; sourceTree = ""; }; 54 | D65A3FEB2383369D00DEBF49 /* NavigationControllerTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControllerTransition.swift; sourceTree = ""; }; 55 | D65A3FEE2383423600DEBF49 /* ContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewController.swift; sourceTree = ""; }; 56 | D65A3FF02383435400DEBF49 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; 57 | D65A3FF22383455B00DEBF49 /* DemoViewControllersByContextFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoViewControllersByContextFactory.swift; sourceTree = ""; }; 58 | D65A3FF4238345AE00DEBF49 /* ChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; 59 | D65A3FF62383479D00DEBF49 /* DemoViewControllerContextTransitionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoViewControllerContextTransitionProvider.swift; sourceTree = ""; }; 60 | D65A3FFB2383490200DEBF49 /* TopViewControllerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopViewControllerProvider.swift; sourceTree = ""; }; 61 | D65A3FFD2383494100DEBF49 /* UITabBarController+TopViewControllerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITabBarController+TopViewControllerProvider.swift"; sourceTree = ""; }; 62 | D65A3FFF23834B1C00DEBF49 /* UINavigationController+TopViewControllerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+TopViewControllerProvider.swift"; sourceTree = ""; }; 63 | D65A4448238401AF00DEBF49 /* NavigationStackChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackChecker.swift; sourceTree = ""; }; 64 | D65A4E632384117400DEBF49 /* LinkParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkParser.swift; sourceTree = ""; }; 65 | /* End PBXFileReference section */ 66 | 67 | /* Begin PBXFrameworksBuildPhase section */ 68 | D65A3FB923832D1400DEBF49 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXFrameworksBuildPhase section */ 76 | 77 | /* Begin PBXGroup section */ 78 | D65A3FB323832D1400DEBF49 = { 79 | isa = PBXGroup; 80 | children = ( 81 | D65A3FBE23832D1400DEBF49 /* NavigationDemo */, 82 | D65A3FBD23832D1400DEBF49 /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | D65A3FBD23832D1400DEBF49 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | D65A3FBC23832D1400DEBF49 /* NavigationDemo.app */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | D65A3FBE23832D1400DEBF49 /* NavigationDemo */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | D65A400123834F6600DEBF49 /* UIKitExtensions */, 98 | D65A3FE22383339E00DEBF49 /* TabBarFactory.swift */, 99 | D65A4E632384117400DEBF49 /* LinkParser.swift */, 100 | D65A3FF82383482200DEBF49 /* DemoImplementations */, 101 | D65A3FED2383420500DEBF49 /* Screens */, 102 | D65A3FD323832D1D00DEBF49 /* Navigator */, 103 | D65A3FBF23832D1400DEBF49 /* AppDelegate.swift */, 104 | D65A3FC823832D1600DEBF49 /* Assets.xcassets */, 105 | D65A3FCA23832D1600DEBF49 /* LaunchScreen.storyboard */, 106 | D65A3FCD23832D1600DEBF49 /* Info.plist */, 107 | ); 108 | path = NavigationDemo; 109 | sourceTree = ""; 110 | }; 111 | D65A3FD323832D1D00DEBF49 /* Navigator */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | D65A3FD423832D3B00DEBF49 /* ViewControllerContext.swift */, 115 | D65A3FD623832D5300DEBF49 /* ViewControllerContextHolder.swift */, 116 | D65A3FD823832D6A00DEBF49 /* ViewControllerContextRouter.swift */, 117 | D65A3FDA23832DA100DEBF49 /* ViewControllersByContextFactory.swift */, 118 | D65A3FDC23832DBB00DEBF49 /* ViewControllerContextTransition.swift */, 119 | D65A3FDE23832DD700DEBF49 /* ViewControllerContextTransitionProvider.swift */, 120 | D65A3FE023832DFF00DEBF49 /* ViewControllerContextSwitcher.swift */, 121 | D65A3FFB2383490200DEBF49 /* TopViewControllerProvider.swift */, 122 | ); 123 | path = Navigator; 124 | sourceTree = ""; 125 | }; 126 | D65A3FED2383420500DEBF49 /* Screens */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | D65A3FE4238334DA00DEBF49 /* ScreenType.swift */, 130 | D65A3FEE2383423600DEBF49 /* ContactsViewController.swift */, 131 | D65A3FF02383435400DEBF49 /* ProfileViewController.swift */, 132 | D65A3FF4238345AE00DEBF49 /* ChatViewController.swift */, 133 | ); 134 | path = Screens; 135 | sourceTree = ""; 136 | }; 137 | D65A3FF82383482200DEBF49 /* DemoImplementations */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | D65A3FE92383364E00DEBF49 /* ModalTransition.swift */, 141 | D65A3FEB2383369D00DEBF49 /* NavigationControllerTransition.swift */, 142 | D65A3FE62383360F00DEBF49 /* TabBarContextSwitcher.swift */, 143 | D65A4448238401AF00DEBF49 /* NavigationStackChecker.swift */, 144 | D65A3FF22383455B00DEBF49 /* DemoViewControllersByContextFactory.swift */, 145 | D65A3FF62383479D00DEBF49 /* DemoViewControllerContextTransitionProvider.swift */, 146 | ); 147 | path = DemoImplementations; 148 | sourceTree = ""; 149 | }; 150 | D65A400123834F6600DEBF49 /* UIKitExtensions */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | D65A3FFD2383494100DEBF49 /* UITabBarController+TopViewControllerProvider.swift */, 154 | D65A3FFF23834B1C00DEBF49 /* UINavigationController+TopViewControllerProvider.swift */, 155 | ); 156 | path = UIKitExtensions; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXGroup section */ 160 | 161 | /* Begin PBXNativeTarget section */ 162 | D65A3FBB23832D1400DEBF49 /* NavigationDemo */ = { 163 | isa = PBXNativeTarget; 164 | buildConfigurationList = D65A3FD023832D1600DEBF49 /* Build configuration list for PBXNativeTarget "NavigationDemo" */; 165 | buildPhases = ( 166 | D65A3FB823832D1400DEBF49 /* Sources */, 167 | D65A3FB923832D1400DEBF49 /* Frameworks */, 168 | D65A3FBA23832D1400DEBF49 /* Resources */, 169 | ); 170 | buildRules = ( 171 | ); 172 | dependencies = ( 173 | ); 174 | name = NavigationDemo; 175 | productName = NavigationDemo; 176 | productReference = D65A3FBC23832D1400DEBF49 /* NavigationDemo.app */; 177 | productType = "com.apple.product-type.application"; 178 | }; 179 | /* End PBXNativeTarget section */ 180 | 181 | /* Begin PBXProject section */ 182 | D65A3FB423832D1400DEBF49 /* Project object */ = { 183 | isa = PBXProject; 184 | attributes = { 185 | LastSwiftUpdateCheck = 1110; 186 | LastUpgradeCheck = 1110; 187 | ORGANIZATIONNAME = MagicLab; 188 | TargetAttributes = { 189 | D65A3FBB23832D1400DEBF49 = { 190 | CreatedOnToolsVersion = 11.1; 191 | }; 192 | }; 193 | }; 194 | buildConfigurationList = D65A3FB723832D1400DEBF49 /* Build configuration list for PBXProject "NavigationDemo" */; 195 | compatibilityVersion = "Xcode 9.3"; 196 | developmentRegion = en; 197 | hasScannedForEncodings = 0; 198 | knownRegions = ( 199 | en, 200 | Base, 201 | ); 202 | mainGroup = D65A3FB323832D1400DEBF49; 203 | productRefGroup = D65A3FBD23832D1400DEBF49 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | D65A3FBB23832D1400DEBF49 /* NavigationDemo */, 208 | ); 209 | }; 210 | /* End PBXProject section */ 211 | 212 | /* Begin PBXResourcesBuildPhase section */ 213 | D65A3FBA23832D1400DEBF49 /* Resources */ = { 214 | isa = PBXResourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | D65A3FCC23832D1600DEBF49 /* LaunchScreen.storyboard in Resources */, 218 | D65A3FC923832D1600DEBF49 /* Assets.xcassets in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXSourcesBuildPhase section */ 225 | D65A3FB823832D1400DEBF49 /* Sources */ = { 226 | isa = PBXSourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | D65A3FE5238334DA00DEBF49 /* ScreenType.swift in Sources */, 230 | D65A3FEC2383369D00DEBF49 /* NavigationControllerTransition.swift in Sources */, 231 | D65A400023834B1C00DEBF49 /* UINavigationController+TopViewControllerProvider.swift in Sources */, 232 | D65A4449238401AF00DEBF49 /* NavigationStackChecker.swift in Sources */, 233 | D65A3FC023832D1400DEBF49 /* AppDelegate.swift in Sources */, 234 | D65A3FDB23832DA100DEBF49 /* ViewControllersByContextFactory.swift in Sources */, 235 | D65A3FF72383479D00DEBF49 /* DemoViewControllerContextTransitionProvider.swift in Sources */, 236 | D65A3FEF2383423600DEBF49 /* ContactsViewController.swift in Sources */, 237 | D65A3FDF23832DD700DEBF49 /* ViewControllerContextTransitionProvider.swift in Sources */, 238 | D65A3FF12383435400DEBF49 /* ProfileViewController.swift in Sources */, 239 | D65A3FE32383339E00DEBF49 /* TabBarFactory.swift in Sources */, 240 | D65A3FE72383360F00DEBF49 /* TabBarContextSwitcher.swift in Sources */, 241 | D65A3FD523832D3B00DEBF49 /* ViewControllerContext.swift in Sources */, 242 | D65A3FF5238345AE00DEBF49 /* ChatViewController.swift in Sources */, 243 | D65A3FFE2383494100DEBF49 /* UITabBarController+TopViewControllerProvider.swift in Sources */, 244 | D65A3FF32383455B00DEBF49 /* DemoViewControllersByContextFactory.swift in Sources */, 245 | D65A3FFC2383490200DEBF49 /* TopViewControllerProvider.swift in Sources */, 246 | D65A4E642384117400DEBF49 /* LinkParser.swift in Sources */, 247 | D65A3FD723832D5300DEBF49 /* ViewControllerContextHolder.swift in Sources */, 248 | D65A3FD923832D6A00DEBF49 /* ViewControllerContextRouter.swift in Sources */, 249 | D65A3FDD23832DBB00DEBF49 /* ViewControllerContextTransition.swift in Sources */, 250 | D65A3FEA2383364E00DEBF49 /* ModalTransition.swift in Sources */, 251 | D65A3FE123832DFF00DEBF49 /* ViewControllerContextSwitcher.swift in Sources */, 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | /* End PBXSourcesBuildPhase section */ 256 | 257 | /* Begin PBXVariantGroup section */ 258 | D65A3FCA23832D1600DEBF49 /* LaunchScreen.storyboard */ = { 259 | isa = PBXVariantGroup; 260 | children = ( 261 | D65A3FCB23832D1600DEBF49 /* Base */, 262 | ); 263 | name = LaunchScreen.storyboard; 264 | sourceTree = ""; 265 | }; 266 | /* End PBXVariantGroup section */ 267 | 268 | /* Begin XCBuildConfiguration section */ 269 | D65A3FCE23832D1600DEBF49 /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | ALWAYS_SEARCH_USER_PATHS = NO; 273 | CLANG_ANALYZER_NONNULL = YES; 274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_ENABLE_OBJC_WEAK = YES; 280 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 281 | CLANG_WARN_BOOL_CONVERSION = YES; 282 | CLANG_WARN_COMMA = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 286 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 287 | CLANG_WARN_EMPTY_BODY = YES; 288 | CLANG_WARN_ENUM_CONVERSION = YES; 289 | CLANG_WARN_INFINITE_RECURSION = YES; 290 | CLANG_WARN_INT_CONVERSION = YES; 291 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 293 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 296 | CLANG_WARN_STRICT_PROTOTYPES = YES; 297 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 298 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 299 | CLANG_WARN_UNREACHABLE_CODE = YES; 300 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 301 | COPY_PHASE_STRIP = NO; 302 | DEBUG_INFORMATION_FORMAT = dwarf; 303 | ENABLE_STRICT_OBJC_MSGSEND = YES; 304 | ENABLE_TESTABILITY = YES; 305 | GCC_C_LANGUAGE_STANDARD = gnu11; 306 | GCC_DYNAMIC_NO_PIC = NO; 307 | GCC_NO_COMMON_BLOCKS = YES; 308 | GCC_OPTIMIZATION_LEVEL = 0; 309 | GCC_PREPROCESSOR_DEFINITIONS = ( 310 | "DEBUG=1", 311 | "$(inherited)", 312 | ); 313 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 314 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 315 | GCC_WARN_UNDECLARED_SELECTOR = YES; 316 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 317 | GCC_WARN_UNUSED_FUNCTION = YES; 318 | GCC_WARN_UNUSED_VARIABLE = YES; 319 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 320 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 321 | MTL_FAST_MATH = YES; 322 | ONLY_ACTIVE_ARCH = YES; 323 | SDKROOT = iphoneos; 324 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 325 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 326 | }; 327 | name = Debug; 328 | }; 329 | D65A3FCF23832D1600DEBF49 /* Release */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ALWAYS_SEARCH_USER_PATHS = NO; 333 | CLANG_ANALYZER_NONNULL = YES; 334 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_ENABLE_OBJC_WEAK = YES; 340 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 341 | CLANG_WARN_BOOL_CONVERSION = YES; 342 | CLANG_WARN_COMMA = YES; 343 | CLANG_WARN_CONSTANT_CONVERSION = YES; 344 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 346 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 356 | CLANG_WARN_STRICT_PROTOTYPES = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 359 | CLANG_WARN_UNREACHABLE_CODE = YES; 360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 363 | ENABLE_NS_ASSERTIONS = NO; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu11; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 374 | MTL_ENABLE_DEBUG_INFO = NO; 375 | MTL_FAST_MATH = YES; 376 | SDKROOT = iphoneos; 377 | SWIFT_COMPILATION_MODE = wholemodule; 378 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 379 | VALIDATE_PRODUCT = YES; 380 | }; 381 | name = Release; 382 | }; 383 | D65A3FD123832D1600DEBF49 /* Debug */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | CODE_SIGN_STYLE = Automatic; 388 | INFOPLIST_FILE = NavigationDemo/Info.plist; 389 | LD_RUNPATH_SEARCH_PATHS = ( 390 | "$(inherited)", 391 | "@executable_path/Frameworks", 392 | ); 393 | PRODUCT_BUNDLE_IDENTIFIER = com.magiclab.navigationdemo; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SWIFT_VERSION = 5.0; 396 | TARGETED_DEVICE_FAMILY = "1,2"; 397 | }; 398 | name = Debug; 399 | }; 400 | D65A3FD223832D1600DEBF49 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 404 | CODE_SIGN_STYLE = Automatic; 405 | INFOPLIST_FILE = NavigationDemo/Info.plist; 406 | LD_RUNPATH_SEARCH_PATHS = ( 407 | "$(inherited)", 408 | "@executable_path/Frameworks", 409 | ); 410 | PRODUCT_BUNDLE_IDENTIFIER = com.magiclab.navigationdemo; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SWIFT_VERSION = 5.0; 413 | TARGETED_DEVICE_FAMILY = "1,2"; 414 | }; 415 | name = Release; 416 | }; 417 | /* End XCBuildConfiguration section */ 418 | 419 | /* Begin XCConfigurationList section */ 420 | D65A3FB723832D1400DEBF49 /* Build configuration list for PBXProject "NavigationDemo" */ = { 421 | isa = XCConfigurationList; 422 | buildConfigurations = ( 423 | D65A3FCE23832D1600DEBF49 /* Debug */, 424 | D65A3FCF23832D1600DEBF49 /* Release */, 425 | ); 426 | defaultConfigurationIsVisible = 0; 427 | defaultConfigurationName = Release; 428 | }; 429 | D65A3FD023832D1600DEBF49 /* Build configuration list for PBXNativeTarget "NavigationDemo" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | D65A3FD123832D1600DEBF49 /* Debug */, 433 | D65A3FD223832D1600DEBF49 /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | /* End XCConfigurationList section */ 439 | }; 440 | rootObject = D65A3FB423832D1400DEBF49 /* Project object */; 441 | } 442 | -------------------------------------------------------------------------------- /NavigationDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NavigationDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NavigationDemo.xcodeproj/project.xcworkspace/xcuserdata/azatzulkarnyaev.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azatZul/NavigationDemo/75749e4997f9fadb6e7812c32dcd5a258592410e/NavigationDemo.xcodeproj/project.xcworkspace/xcuserdata/azatzulkarnyaev.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /NavigationDemo.xcodeproj/project.xcworkspace/xcuserdata/azatzulkarnyaev.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /NavigationDemo.xcodeproj/xcuserdata/azatzulkarnyaev.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | NavigationDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /NavigationDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | 28 | var window: UIWindow? 29 | 30 | lazy var tabsSwitcher = TabBarContextSwitcher() 31 | lazy var router: ViewControllerContextRouterProtocol = { 32 | let topViewControllerProvider = DefaultTopViewControllerProvider() 33 | let navigationStackChanger = NavigationStackChanger(topViewControllerProvider: topViewControllerProvider) 34 | let viewControllersFactory = DemoViewControllersByContextFactory() 35 | let router = ViewControllerContextRouter(topViewControllerProvider: DefaultTopViewControllerProvider(), 36 | viewControllersFactory: viewControllersFactory, 37 | transitionProvider: DemoViewControllerContextTransitionProvider(), 38 | contextSwitchers: [tabsSwitcher, navigationStackChanger]) 39 | viewControllersFactory.globalRouter = router 40 | return router 41 | }() 42 | 43 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 44 | let tabBar = TabBarFactory(globalRouter: router).makeTabBar() 45 | self.tabsSwitcher.tabBar = tabBar 46 | let window = UIWindow() 47 | window.rootViewController = tabBar 48 | self.window = window 49 | window.makeKeyAndVisible() 50 | return true 51 | } 52 | 53 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 54 | let parser = LinkParser() 55 | guard let context = parser.viewControllerContext(from: url) else { return false } 56 | self.router.navigateToContext(context, animated: true) 57 | return true 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /NavigationDemo/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 | } -------------------------------------------------------------------------------- /NavigationDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /NavigationDemo/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 | -------------------------------------------------------------------------------- /NavigationDemo/DemoImplementations/DemoViewControllerContextTransitionProvider.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import Foundation 24 | 25 | final class DemoViewControllerContextTransitionProvider: ViewControllerContextTransitionProvider { 26 | func navigation(for context: ViewControllerContext) -> ViewControllerContextTransition { 27 | guard let screenType = ScreenType(rawValue: context.screenType) else { return ModalTransition() } 28 | switch screenType { 29 | case .chat: 30 | return NavigationControllerTransition() 31 | case .contacts, .profile: 32 | return ModalTransition() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /NavigationDemo/DemoImplementations/DemoViewControllersByContextFactory.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class DemoViewControllersByContextFactory: ViewControllersByContextFactory { 26 | var globalRouter: ViewControllerContextRouterProtocol? 27 | 28 | func viewController(for context: ViewControllerContext) -> UIViewController? { 29 | guard let screenType = ScreenType(rawValue: context.screenType) else { return nil } 30 | switch screenType { 31 | case .chat: 32 | guard let chatId = context.info as? String else { return nil } 33 | return ChatViewController(chatId: chatId) 34 | case .contacts: 35 | return ContactsViewController() 36 | case .profile: 37 | guard let globalRouter = globalRouter else { return nil } 38 | return ProfileViewController(globalRouter: globalRouter) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /NavigationDemo/DemoImplementations/ModalTransition.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class ModalTransition: ViewControllerContextTransition { 26 | func navigate(from source: UIViewController?, to destination: UIViewController, animated: Bool) { 27 | source?.present(destination, animated: animated, completion: nil) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NavigationDemo/DemoImplementations/NavigationControllerTransition.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class NavigationControllerTransition: ViewControllerContextTransition { 26 | func navigate(from source: UIViewController?, to destination: UIViewController, animated: Bool) { 27 | guard let navigationController = source?.navigationController else { 28 | assertionFailure("Attempting to push to navigation controller without having it") 29 | return 30 | } 31 | navigationController.pushViewController(destination, animated: animated) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /NavigationDemo/DemoImplementations/NavigationStackChecker.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class NavigationStackChanger: ViewControllerContextSwitcher { 26 | private let topViewControllerProvider: TopViewControllerProvider 27 | private var topNavigationController: UINavigationController? { 28 | let topController = topViewControllerProvider.topViewController 29 | return (topController as? UINavigationController) ?? topController?.navigationController 30 | } 31 | 32 | init(topViewControllerProvider: TopViewControllerProvider) { 33 | self.topViewControllerProvider = topViewControllerProvider 34 | } 35 | 36 | func canSwitch(to context: ViewControllerContext) -> Bool { 37 | return self.controllerInStack(with: context) != nil 38 | } 39 | 40 | func switchContext(to context: ViewControllerContext, animated: Bool) { 41 | guard let controllerToPop = self.controllerInStack(with: context) else { return } 42 | topNavigationController?.popToViewController(controllerToPop, animated: animated) 43 | } 44 | 45 | private func controllerInStack(with context: ViewControllerContext) -> UIViewController? { 46 | guard let navigationController = topNavigationController else { return nil } 47 | return navigationController.viewControllers.first { controller in 48 | guard let controller = controller as? ViewControllerContextHolder else { return false } 49 | return controller.currentContext == context 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /NavigationDemo/DemoImplementations/TabBarContextSwitcher.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class TabBarContextSwitcher: ViewControllerContextSwitcher { 26 | var tabBar: UITabBarController? 27 | 28 | private let tabsMap: [String: Int] = [ 29 | ScreenType.contacts.rawValue : 0, 30 | ScreenType.profile.rawValue: 1 31 | ] 32 | 33 | func canSwitch(to context: ViewControllerContext) -> Bool { 34 | guard let screenType = ScreenType(rawValue: context.screenType) else { return false } 35 | return screenType == .profile || screenType == .contacts 36 | } 37 | 38 | func switchContext(to context: ViewControllerContext, animated: Bool) { 39 | guard canSwitch(to: context) else { return } 40 | guard let index = tabsMap[context.screenType] else { return } 41 | tabBar?.selectedIndex = index 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /NavigationDemo/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.magiclab.navigationdemo 26 | CFBundleURLSchemes 27 | 28 | navdemo 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | LSRequiresIPhoneOS 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /NavigationDemo/LinkParser.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import Foundation 24 | 25 | final class LinkParser { 26 | func viewControllerContext(from link: URL) -> ViewControllerContext? { 27 | guard let components = URLComponents(string: link.absoluteString), let typePart = components.host, let screenType = ScreenType(rawValue: typePart) else { return nil } 28 | return makeContext(of: screenType, queryItems: components.queryItems ?? []) 29 | } 30 | 31 | private func makeContext(of screenType: ScreenType, queryItems: [URLQueryItem]?) -> ViewControllerContext? { 32 | switch screenType { 33 | case .chat: 34 | guard let query = queryItems?.first, query.name == "chatId", let chatId = query.value else { return nil } 35 | return ViewControllerContext(screenType: screenType.rawValue, info: chatId) 36 | case .contacts, .profile: 37 | return ViewControllerContext(screenType: screenType.rawValue, info: nil) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/TopViewControllerProvider.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | protocol TopViewControllerProvider { 26 | var topViewController: UIViewController? { get } 27 | } 28 | 29 | typealias RootViewControllerProvider = () -> UIViewController? 30 | 31 | final class DefaultTopViewControllerProvider: TopViewControllerProvider { 32 | private let rootViewControllerProvider: RootViewControllerProvider 33 | 34 | init(rootViewControllerProvider: @escaping RootViewControllerProvider = { UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController }) { 35 | self.rootViewControllerProvider = rootViewControllerProvider 36 | } 37 | 38 | var topViewController: UIViewController? { 39 | guard let root = self.rootViewControllerProvider() else { return nil } 40 | return self.traverseViewControllersHierarchy(startingFrom: root) 41 | } 42 | 43 | private func traverseViewControllersHierarchy(startingFrom root: UIViewController) -> UIViewController? { 44 | var currentViewController: UIViewController? = root 45 | while currentViewController != nil { 46 | if let customContainer = currentViewController as? TopViewControllerProvider { 47 | currentViewController = customContainer.topViewController 48 | } else if let presentedController = currentViewController?.presentedViewController { 49 | currentViewController = presentedController 50 | } else { 51 | break 52 | } 53 | } 54 | return currentViewController 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/ViewControllerContext.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | protocol ViewControllerContextInfo { 26 | func isEqual(to info: ViewControllerContextInfo?) -> Bool 27 | } 28 | 29 | extension ViewControllerContextInfo where Self: Equatable { 30 | func isEqual(to info: ViewControllerContextInfo?) -> Bool { 31 | guard let info = info as? Self else { return false } 32 | return self == info 33 | } 34 | } 35 | 36 | struct ViewControllerContext: Equatable { 37 | public let screenType: String 38 | public let info: ViewControllerContextInfo? 39 | 40 | static func == (lhs: ViewControllerContext, rhs: ViewControllerContext) -> Bool { 41 | guard lhs.screenType == rhs.screenType else { return false } 42 | guard (lhs.info != nil) || (rhs.info != nil) else { return true } 43 | if let lInfo = lhs.info { 44 | return lInfo.isEqual(to: rhs.info) 45 | } else if let rInfo = rhs.info { 46 | return rInfo.isEqual(to: lhs.info) 47 | } 48 | return true 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/ViewControllerContextHolder.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import Foundation 24 | 25 | protocol ViewControllerContextHolder { 26 | var currentContext: ViewControllerContext? { get } 27 | } 28 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/ViewControllerContextRouter.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import Foundation 24 | 25 | protocol ViewControllerContextRouterProtocol { 26 | func navigateToContext(_ context: ViewControllerContext, animated: Bool) 27 | } 28 | 29 | final class ViewControllerContextRouter: ViewControllerContextRouterProtocol { 30 | private let topViewControllerProvider: TopViewControllerProvider 31 | private let viewControllersFactory: ViewControllersByContextFactory 32 | private let transitionProvider: ViewControllerContextTransitionProvider 33 | private let contextSwitchers: [ViewControllerContextSwitcher] 34 | 35 | init(topViewControllerProvider: TopViewControllerProvider, 36 | viewControllersFactory: ViewControllersByContextFactory, 37 | transitionProvider: ViewControllerContextTransitionProvider, 38 | contextSwitchers: [ViewControllerContextSwitcher]) { 39 | self.topViewControllerProvider = topViewControllerProvider 40 | self.viewControllersFactory = viewControllersFactory 41 | self.transitionProvider = transitionProvider 42 | self.contextSwitchers = contextSwitchers 43 | } 44 | 45 | func navigateToContext(_ context: ViewControllerContext, animated: Bool) { 46 | let topViewController = self.topViewControllerProvider.topViewController 47 | if let contextHolder = topViewController as? ViewControllerContextHolder, contextHolder.currentContext == context { 48 | return 49 | } 50 | if let switcher = self.contextSwitchers.first(where: { $0.canSwitch(to: context) }) { 51 | switcher.switchContext(to: context, animated: animated) 52 | return 53 | } 54 | guard let viewController = self.viewControllersFactory.viewController(for: context) else { return } 55 | let navigation = self.transitionProvider.navigation(for: context) 56 | navigation.navigate(from: self.topViewControllerProvider.topViewController, 57 | to: viewController, 58 | animated: true) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/ViewControllerContextSwitcher.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import Foundation 24 | 25 | protocol ViewControllerContextSwitcher { 26 | func canSwitch(to context: ViewControllerContext) -> Bool 27 | func switchContext(to context: ViewControllerContext, animated: Bool) 28 | } 29 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/ViewControllerContextTransition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerContextTransition.swift 3 | // NavigationDemo 4 | // 5 | // Created by Azat Zulkarnyaev on 18/11/2019. 6 | // Copyright © 2019 MagicLab. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ViewControllerContextTransition { 12 | func navigate(from source: UIViewController?, 13 | to destination: UIViewController, 14 | animated: Bool) 15 | } 16 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/ViewControllerContextTransitionProvider.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import Foundation 24 | 25 | protocol ViewControllerContextTransitionProvider { 26 | func navigation(for context: ViewControllerContext) -> ViewControllerContextTransition 27 | } 28 | -------------------------------------------------------------------------------- /NavigationDemo/Navigator/ViewControllersByContextFactory.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | protocol ViewControllersByContextFactory { 26 | func viewController(for context: ViewControllerContext) -> UIViewController? 27 | } 28 | -------------------------------------------------------------------------------- /NavigationDemo/Screens/ChatViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class ChatViewController: UIViewController { 26 | private let chatId: String 27 | 28 | init(chatId: String) { 29 | self.chatId = chatId 30 | super.init(nibName: nil, bundle: nil) 31 | view.backgroundColor = .white 32 | title = chatId 33 | } 34 | 35 | required init?(coder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | } 39 | 40 | extension ChatViewController: ViewControllerContextHolder { 41 | var currentContext: ViewControllerContext? { 42 | return ViewControllerContext(screenType: ScreenType.chat.rawValue, info: chatId) 43 | } 44 | } 45 | 46 | extension String: ViewControllerContextInfo {} 47 | -------------------------------------------------------------------------------- /NavigationDemo/Screens/ContactsViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class ContactsViewController: UIViewController { 26 | private let contacts = ["Contact 1", "Contact 2", "Contact 3"] 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | self.title = "Contacts" 31 | self.configureList() 32 | } 33 | 34 | private func configureList() { 35 | let tableView = UITableView(frame: view.bounds) 36 | view.addSubview(tableView) 37 | tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 38 | tableView.tableFooterView = nil 39 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 40 | tableView.dataSource = self 41 | tableView.delegate = self 42 | } 43 | } 44 | 45 | extension ContactsViewController: UITableViewDelegate, UITableViewDataSource { 46 | 47 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 48 | return contacts.count 49 | } 50 | 51 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 52 | let title = self.contacts[indexPath.row] 53 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 54 | cell.textLabel?.text = title 55 | return cell 56 | } 57 | 58 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 59 | let title = contacts[indexPath.row] 60 | let chatViewController = ChatViewController(chatId: title) 61 | navigationController?.pushViewController(chatViewController, animated: true) 62 | } 63 | } 64 | 65 | extension ContactsViewController: ViewControllerContextHolder { 66 | var currentContext: ViewControllerContext? { 67 | return ViewControllerContext(screenType: ScreenType.contacts.rawValue, info: nil) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /NavigationDemo/Screens/ProfileViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class ProfileViewController: UIViewController { 26 | private let globalRouter: ViewControllerContextRouterProtocol 27 | 28 | init(globalRouter: ViewControllerContextRouterProtocol) { 29 | self.globalRouter = globalRouter 30 | super.init(nibName: nil, bundle: nil) 31 | } 32 | 33 | required init?(coder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | self.title = "Profile" 40 | self.view.backgroundColor = .white 41 | self.addNavigationButton() 42 | } 43 | 44 | private func addNavigationButton() { 45 | let button = UIButton() 46 | button.translatesAutoresizingMaskIntoConstraints = false 47 | button.setTitle("Go to Contact 1", for: .normal) 48 | button.setTitleColor(.black, for: .normal) 49 | self.view.addSubview(button) 50 | 51 | NSLayoutConstraint.activate([ 52 | button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), 53 | button.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20), 54 | button.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20), 55 | ]) 56 | button.addTarget(self, action: #selector(onNavigationButtonTap), for: .touchUpInside) 57 | } 58 | 59 | @objc 60 | private func onNavigationButtonTap() { 61 | let context1 = ViewControllerContext(screenType: ScreenType.contacts.rawValue, info: nil) 62 | self.globalRouter.navigateToContext(context1, animated: true) 63 | let context2 = ViewControllerContext(screenType: ScreenType.chat.rawValue, info: "Contact 1") 64 | self.globalRouter.navigateToContext(context2, animated: true) 65 | } 66 | } 67 | 68 | extension ProfileViewController: ViewControllerContextHolder { 69 | var currentContext: ViewControllerContext? { 70 | return ViewControllerContext(screenType: ScreenType.profile.rawValue, info: nil) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /NavigationDemo/Screens/ScreenType.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import Foundation 24 | 25 | enum ScreenType: String { 26 | case contacts = "contacts" 27 | case profile = "profile" 28 | case chat = "chat" 29 | } 30 | -------------------------------------------------------------------------------- /NavigationDemo/TabBarFactory.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | final class TabBarFactory { 26 | private let globalRouter: ViewControllerContextRouterProtocol 27 | 28 | init(globalRouter: ViewControllerContextRouterProtocol) { 29 | self.globalRouter = globalRouter 30 | } 31 | 32 | func makeTabBar() -> UITabBarController { 33 | let tabBarController = UITabBarController() 34 | let contactsViewController = UINavigationController(rootViewController: ContactsViewController()) 35 | contactsViewController.tabBarItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 1) 36 | let profileViewController = UINavigationController(rootViewController: ProfileViewController(globalRouter: globalRouter)) 37 | profileViewController.tabBarItem = UITabBarItem(tabBarSystemItem: .more, tag: 2) 38 | 39 | tabBarController.setViewControllers([contactsViewController, profileViewController], animated: false) 40 | return tabBarController 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /NavigationDemo/UIKitExtensions/UINavigationController+TopViewControllerProvider.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | extension UINavigationController: TopViewControllerProvider {} 26 | -------------------------------------------------------------------------------- /NavigationDemo/UIKitExtensions/UITabBarController+TopViewControllerProvider.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2019 Azat Zulkarniaev 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 | 23 | import UIKit 24 | 25 | extension UITabBarController: TopViewControllerProvider { 26 | var topViewController: UIViewController? { 27 | return self.selectedViewController 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NavigationDemo 2 | 3 | This is a demo app for showing use-case of the ideas described in the [article](https://badootech.badoo.com/routing-for-ios-universal-navigation-without-rewriting-the-app-215b52a37cf2). 4 | 5 | To run the project open `NavigationDemo.xcodeproj`, build and run the app for the desired device. 6 | 7 | You also can try to open URL links with scheme `navdemo://` to test the navigation. URL format should be like 8 | 9 | - `navdemo://chat?chatId=` 10 | - `navdemo://contacts` 11 | - `navdemo://profile` 12 | 13 | Alternatively you can manually create `ViewControllerContext` and pass it to the `navigate(to:)` function of the `ViewControllerContextRouter` object. 14 | --------------------------------------------------------------------------------