├── .gitignore ├── .swift-version ├── .travis.yml ├── Example ├── AppDelegate.swift ├── AppRoutes.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── Login.imageset │ │ ├── Contents.json │ │ ├── login-1.png │ │ ├── login-2.png │ │ └── login.png │ ├── Logout.imageset │ │ ├── Contents.json │ │ ├── logout-1.png │ │ ├── logout-2.png │ │ └── logout.png │ ├── Privileged.imageset │ │ ├── Contents.json │ │ ├── privilege-1.png │ │ ├── privilege-2.png │ │ └── privilege.png │ ├── Secret.imageset │ │ ├── Contents.json │ │ ├── secret-1.png │ │ ├── secret-2.png │ │ └── secret.png │ └── Settings.imageset │ │ ├── Contents.json │ │ ├── settings-1.png │ │ ├── settings-2.png │ │ └── settings.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── HomeViewController.swift ├── Info.plist ├── LoginViewController.swift ├── PrivilegedInfoViewController.swift ├── SecretViewController.swift └── SettingsViewController.swift ├── LICENSE ├── Package.swift ├── README.md ├── Routing.podspec ├── Routing.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Example.xcscheme │ ├── Routing OSX.xcscheme │ ├── Routing iOS.xcscheme │ ├── Routing tvOS.xcscheme │ └── Routing watchOS.xcscheme ├── Routing.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Source ├── Info.plist ├── Routable.swift ├── Routing.h ├── Routing.swift └── iOS.swift └── Tests ├── Info.plist └── RoutingTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | build/ 4 | DerivedData 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | *.xcuserstate 17 | *.xcscmblueprint 18 | 19 | Carthage/Build 20 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | 4 | install: 5 | - gem install xcpretty 6 | 7 | env: 8 | global: 9 | - LC_CTYPE=en_US.UTF-8 10 | - LANG=en_US.UTF-8 11 | - FRAMEWORK_NAME="Routing" 12 | - IOS_SDK=iphonesimulator12.2 13 | - OSX_SDK=macosx10.14 14 | - TVOS_SDK=appletvsimulator12.2 15 | - WATCHOS_SDK=watchsimulator5.2 16 | matrix: 17 | - DESTINATION="OS=12.2,name=iPhone X" SCHEME="iOS" SDK="$IOS_SDK" ACTION="test" 18 | - DESTINATION="arch=x86_64" SCHEME="OSX" SDK="$OSX_SDK" ACTION="test" 19 | - DESTINATION="OS=12.2,name=Apple TV" SCHEME="tvOS" SDK="$TVOS_SDK" ACTION="build" 20 | - DESTINATION="OS=5.2,name=Apple Watch Series 4 - 44mm" SCHEME="watchOS" SDK="$WATCHOS_SDK" ACTION="build" 21 | 22 | script: 23 | - set -o pipefail 24 | - xcodebuild -version 25 | - xcodebuild -showsdks 26 | - xcodebuild 27 | -project "$FRAMEWORK_NAME.xcodeproj" 28 | -scheme "$FRAMEWORK_NAME $SCHEME" 29 | -sdk "$SDK" 30 | -destination "$DESTINATION" 31 | -configuration Debug 32 | ONLY_ACTIVE_ARCH=NO 33 | "$ACTION" 34 | | xcpretty -c 35 | 36 | after_success: 37 | - bash <(curl -s https://codecov.io/bash) 38 | 39 | -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Routing 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | registerRoutes() 10 | return true 11 | } 12 | 13 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool { 14 | return router["Views"].open(url) 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Example/AppRoutes.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Routing 3 | 4 | public let router = Routing() 5 | 6 | public func registerRoutes() { 7 | let presentationSetup: PresentationSetup = { vc, _, _ in 8 | vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, 9 | target: vc, 10 | action: #selector(vc.cancel)) 11 | } 12 | 13 | router.map("routingexample://push/login", 14 | source: .storyboard(storyboard: "Main", identifier: "LoginViewController", bundle: nil), 15 | style: .push(animated: true)) 16 | 17 | router.map("routingexample://present/login", 18 | source: .storyboard(storyboard: "Main", identifier: "LoginViewController", bundle: nil), 19 | style: .inNavigationController(.present(animated: true)), 20 | setup: presentationSetup) 21 | 22 | router.map("routingexample://push/privilegedinfo", 23 | source: .storyboard(storyboard: "Main", identifier: "PrivilegedInfoViewController", bundle: nil), 24 | style: .push(animated: true)) 25 | 26 | router.map("routingexample://present/privilegedinfo", 27 | source: .storyboard(storyboard: "Main", identifier: "PrivilegedInfoViewController", bundle: nil), 28 | style: .inNavigationController(.present(animated: true)), 29 | setup: presentationSetup) 30 | 31 | router.map("routingexample://push/settings", 32 | source: .storyboard(storyboard: "Main", identifier: "SettingsViewController", bundle: nil), 33 | style: .push(animated: true)) 34 | 35 | router.map("routingexample://present/settings", 36 | source: .storyboard(storyboard: "Main", identifier: "SettingsViewController", bundle: nil), 37 | style: .inNavigationController(.present(animated: true)), 38 | setup: presentationSetup) 39 | 40 | router.proxy("routingexample://*", tags: ["Views"]) { route, _, _, next in 41 | if shouldPresentViewControllers { 42 | let route = route.replacingOccurrences(of: "push", with: "present") 43 | next((route, Parameters(), nil)) 44 | } else { 45 | next(nil) 46 | } 47 | } 48 | 49 | router.proxy("/*/privilegedinfo", tags: ["Views"]) { route, parameters, any, next in 50 | if authenticated { 51 | next(nil) 52 | } else { 53 | next(("routingexample://present/login?callback=\(route)", parameters, any)) 54 | } 55 | } 56 | 57 | router.proxy("/*", tags: ["Views"]) { route, parameters, any, next in 58 | print("opened: route (\(route)) with parameters (\(parameters)) & passing (\(String(describing: any)))") 59 | next(nil) 60 | } 61 | } 62 | 63 | extension UIViewController { 64 | @objc func cancel() { 65 | self.dismiss(animated: true, completion: nil) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Login.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "login.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "login-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "login-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Login.imageset/login-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Login.imageset/login-1.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Login.imageset/login-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Login.imageset/login-2.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Login.imageset/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Login.imageset/login.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Logout.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logout.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "logout-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "logout-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Logout.imageset/logout-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Logout.imageset/logout-1.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Logout.imageset/logout-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Logout.imageset/logout-2.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Logout.imageset/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Logout.imageset/logout.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Privileged.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "privilege.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "privilege-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "privilege-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Privileged.imageset/privilege-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Privileged.imageset/privilege-1.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Privileged.imageset/privilege-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Privileged.imageset/privilege-2.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Privileged.imageset/privilege.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Privileged.imageset/privilege.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Secret.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "secret.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "secret-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "secret-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Secret.imageset/secret-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Secret.imageset/secret-1.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Secret.imageset/secret-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Secret.imageset/secret-2.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Secret.imageset/secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Secret.imageset/secret.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Settings.imageset/settings-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Settings.imageset/settings-1.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Settings.imageset/settings-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Settings.imageset/settings-2.png -------------------------------------------------------------------------------- /Example/Assets.xcassets/Settings.imageset/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Settings.imageset/settings.png -------------------------------------------------------------------------------- /Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | -------------------------------------------------------------------------------- /Example/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class HomeViewController: UITableViewController { 4 | fileprivate enum Row: Int { 5 | case login 6 | case logout 7 | case privilegedInfo 8 | case settings 9 | } 10 | 11 | override func viewWillAppear(_ animated: Bool) { 12 | super.viewWillAppear(animated) 13 | self.tableView.reloadData() 14 | } 15 | } 16 | 17 | // MARK: Table View Delegate 18 | extension HomeViewController { 19 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 20 | switch Row(rawValue: (indexPath as NSIndexPath).row)! { 21 | case .login: 22 | router["Views"].open("routingexample://push/login") 23 | case .logout: 24 | authenticated = false 25 | self.tableView.reloadData() 26 | case .privilegedInfo: 27 | router["Views"].open("routingexample://push/privilegedinfo", passing: ["opened from the home view": Date()]) 28 | case .settings: 29 | router["Views"].open("routingexample://push/settings") 30 | } 31 | } 32 | } 33 | 34 | // MARK: Table View Datasource 35 | extension HomeViewController { 36 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 37 | switch Row(rawValue: (indexPath as NSIndexPath).row)! { 38 | case .login where authenticated == true: fallthrough 39 | case .logout where authenticated == false: 40 | return 0.0 41 | default: 42 | return 44.0 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.routing.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.4.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Editor 26 | CFBundleURLName 27 | com.routing 28 | CFBundleURLSchemes 29 | 30 | routingexample 31 | 32 | 33 | 34 | CFBundleVersion 35 | $(CURRENT_PROJECT_VERSION) 36 | LSRequiresIPhoneOS 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIMainStoryboardFile 41 | Main 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Routing 3 | 4 | var authenticated = false 5 | 6 | class LoginViewController: UIViewController, RoutingPresentationSetup { 7 | @IBOutlet weak var username: UITextField! 8 | @IBOutlet weak var password: UITextField! 9 | var callback: String? 10 | 11 | func setup(_ route: String, with parameters: Parameters, passing any: Any?) { 12 | if let callbackURL = parameters["callback"] { 13 | self.callback = callbackURL 14 | } 15 | } 16 | 17 | @IBAction func login() { 18 | guard let username = username.text, let password = password.text , username != "" && password != "" else { 19 | return 20 | } 21 | 22 | let completion = { 23 | if let callback = self.callback { 24 | router.open(callback, passing: ["opened from login": Date()]) 25 | } 26 | } 27 | 28 | authenticated = true 29 | if isModal { 30 | self.dismiss(animated: true, completion: completion) 31 | } else { 32 | _ = self.navigationController?.popViewControllerAnimated(true, completion: completion) 33 | } 34 | } 35 | } 36 | 37 | extension LoginViewController { 38 | var isModal: Bool { 39 | if let presented = self.presentingViewController?.presentedViewController, 40 | let navigationController = self.navigationController 41 | , presented == navigationController { 42 | return true 43 | } 44 | return false 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Example/PrivilegedInfoViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Routing 3 | 4 | class PrivilegedInfoViewController: UIViewController, RouteOwner { 5 | var routeUUID: RouteUUID = "" 6 | 7 | override func viewDidLoad() { 8 | router.map("routingexample://push/secret", 9 | owner: self, 10 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil), 11 | style: .push(animated: true)) 12 | 13 | routeUUID = router.map("routingexample://present/secret", 14 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil), 15 | style: .inNavigationController(.present(animated: true))) { vc, _, _ in 16 | vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, 17 | target: vc, 18 | action: #selector(vc.cancel)) 19 | } 20 | } 21 | 22 | deinit { 23 | router.dispose(of: routeUUID) 24 | } 25 | } 26 | 27 | extension PrivilegedInfoViewController: RoutingPresentationSetup { 28 | 29 | func setup(_ route: String, with parameters: Parameters, passing any: Any?) { 30 | if let any = any as? [String: Date] { 31 | print("Passed date: \(any)") 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Example/SecretViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SecretViewController: UIViewController {} 4 | -------------------------------------------------------------------------------- /Example/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | var shouldPresentViewControllers = false 4 | 5 | class SettingsViewController: UITableViewController { 6 | @IBOutlet weak var presentViewControllers: UISwitch! 7 | override func viewDidLoad() { 8 | super.viewDidLoad() 9 | self.presentViewControllers.setOn(shouldPresentViewControllers, animated: false) 10 | } 11 | 12 | @IBAction func presentViewControllersChanged(_ sender: UISwitch) { 13 | shouldPresentViewControllers = sender.isOn 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jason Prasad 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Routing" 5 | ) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | [![Build Status](https://travis-ci.org/jjgp/Routing.svg?branch=master)](https://travis-ci.org/jjgp/Routing) 4 | [![Code coverage status](https://img.shields.io/codecov/c/github/jjgp/Routing.svg?style=flat-square)](http://codecov.io/github/jjgp/Routing) 5 | [![Platform support](https://img.shields.io/badge/platform-ios%20%7C%20osx%20%7C%20tvos%20%7C%20watchos-lightgrey.svg?style=flat-square)](https://img.shields.io/badge/platform-ios%20%7C%20osx%20%7C%20tvos%20%7C%20watchos-lightgrey.svg?style=flat-square) 6 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Routing.svg)](https://cocoapods.org/pods/Routing) 8 | [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/Routing/Routing/blob/master/LICENSE) 9 | 10 | ## Usage 11 | 12 | Let's say you have a table view controller that displays privileged information once a user selects a cell. An implementation of tableView:didSelectRowAtIndexPath: may look as such. 13 | 14 | ```swift 15 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 16 | switch Row(rawValue: indexPath.row)! { 17 | // ... 18 | case .PrivilegedInfo: 19 | router["Views"].open("routingexample://push/privilegedinfo") 20 | } 21 | // ... 22 | } 23 | ``` 24 | 25 | Perhaps the privileged information is only available after a user authenticates with the service. After logging in we want the privileged information presented to the user right away. Without changing the above implementation we may proxy the intent and display a log in view, after which, a call back may present the privileged information screen. 26 | 27 | ```swift 28 | router.proxy("/*/privilegedinfo", tags: ["Views"]) { route, parameters, any, next in 29 | if authenticated { 30 | next(nil) 31 | } else { 32 | next(("routingexample://present/login?callback=\(route)", parameters, any)) 33 | } 34 | } 35 | ``` 36 | 37 | ![Routing Proxy](http://i.giphy.com/l0MYulEzZgjlDkI1y.gif) 38 | 39 | Eventually we may need to support a deep link to the privileged information from outside of the application. This can be handled in the AppDelegate simply as follows. 40 | 41 | ```swift 42 | func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool { 43 | return router["Views"].open(url) 44 | } 45 | ``` 46 | 47 | ![Routing Deep Link](http://i.giphy.com/3o6ZtoFBVCKafruVMs.gif) 48 | 49 | An example of other routes in an application may look like this. 50 | 51 | ```swift 52 | let presentationSetup: PresentationSetup = { vc, _, _ in 53 | vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, 54 | target: vc, 55 | action: #selector(vc.cancel)) 56 | } 57 | 58 | router.map("routingexample://present/login", 59 | source: .storyboard(storyboard: "Main", identifier: "LoginViewController", bundle: nil), 60 | style: .inNavigationController(.present(animated: true)), 61 | setup: presentationSetup) 62 | 63 | router.map("routingexample://push/privilegedinfo", 64 | source: .storyboard(storyboard: "Main", identifier: "PrivilegedInfoViewController", bundle: nil), 65 | style: .push(animated: true)) 66 | 67 | router.map("routingexample://present/settings", 68 | source: .storyboard(storyboard: "Main", identifier: "SettingsViewController", bundle: nil), 69 | style: .inNavigationController(.present(animated: true)), 70 | setup: presentationSetup) 71 | 72 | router.proxy("/*", tags: ["Views"]) { route, parameters, any, next in 73 | print("opened: route (\(route)) with parameters (\(parameters)) & passing (\(any))") 74 | next(nil) 75 | } 76 | ``` 77 | 78 | At its simplest, Routing allows the association of string patterns to closures. This allows for the expression of intent in certain areas of code and the implementation of it in another. UI may only be concerned with expressing the intent of transitioning to another view and the business logic may be handled elsewhere. Routing allows for the explicit documentation of an application's behavior and views through mappings and proxies. 79 | 80 | ## Installation 81 | 82 | ### CocoaPods 83 | 84 | Via [CocoaPods](https://cocoapods.org/pods/Routing): 85 | 86 | ```ruby 87 | source 'https://github.com/CocoaPods/Specs.git' 88 | platform :ios, '10.0' 89 | use_frameworks! 90 | 91 | pod 'Routing', '~> 1.4.0' 92 | ``` 93 | 94 | ### Carthage 95 | 96 | Via [Carthage](https://github.com/Carthage/Carthage): 97 | 98 | ```ogdl 99 | github "jjgp/Routing" 100 | ``` 101 | ## Further Detail 102 | 103 | ### Map 104 | 105 | A router instance may map a string pattern to view controller navigation, as covered in the [Usage](#usage) section above, or just a closure as presented below. The closure will have four parameters. The route it matched, the parameters (both query and segments in the URL), any data passed through open, and a completion closure that must be called or the router will halt all subsequent calls to *#open*. 106 | 107 | ```swift 108 | router.map("routingexample://route/:argument") { route, parameters, any, completed in 109 | argument = parameters["argument"] 110 | completed() 111 | } 112 | ``` 113 | 114 | ### Proxy 115 | 116 | A router instance may proxy any string pattern. The closure will also have four parameters. The route it matched, the parameters, any data passed, and a next closure. The next closure accepts a *ProxyCommit?* tuple with arguments *String*, *Parameters*, and *Any?*. If nil is passed to *Next* then the router will continue to another proxy if it exists or subsequently to a mapped route. If a proxy were to pass a *ProxyCommit* tuple to the next closure, the router will skip any subsequent proxy and attempt to match a mapped route. Failure to call next will halt the router and all subsequent calls to *#open*. 117 | 118 | ```swift 119 | router.proxy("routingexample://route/one") { route, parameters, any, next -> Void in 120 | next(("routingexample://route/two", parameters, any)) 121 | } 122 | ``` 123 | 124 | ### Order of Map or Proxy 125 | 126 | In general, the last call to register a map or proxy to the router will be first called in the event of a matched URL respectively. Proxies will be serviced first and then a map. 127 | 128 | ### Tags 129 | 130 | A tag may be passed to maps or proxies. The default tag for maps to view controller navigation is *"Views"*. Tags allow for the router to be subscripted to a specific context. If a router is subscripted with *"Views"*, then it will only attempt to find routes that are tagged as such. 131 | 132 | ```swift 133 | router.proxy("/*", tags: ["Views, Logs"]) { route, parameters, any, next in 134 | print("opened: route (\(route)) with parameters (\(parameters)) & passing (\(any))") 135 | next(nil) 136 | } 137 | 138 | router["Views", "Logs", "Actions"].open(url) 139 | 140 | router["Views"].open(url, passing: NSDate()) // pass any data if needed 141 | 142 | router.open(url) // - or - to search all routes... 143 | 144 | ``` 145 | 146 | ### Route Owner 147 | 148 | Routes may have a *RouteOwner* specified when using *#map* or *#proxy*. When the *RouteOwner* is deallocated, the route is removed from the *Routing* instance. 149 | 150 | ```swift 151 | public protocol RouteOwner: class {} 152 | 153 | class PrivilegedInfoViewController: UIViewController, RouteOwner { 154 | override func viewDidLoad() { 155 | router.map("routingexample://secret", 156 | owner: self, 157 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil), 158 | style: .push(animated: true)) 159 | } 160 | } 161 | ``` 162 | 163 | ### RouteUUID and Disposing of a Route 164 | 165 | When a route is added via *#map* or *#proxy*, a *RouteUUID* is returned. This *RouteUUID* can be used to dispose of the route. 166 | 167 | ```swift 168 | routeUUID = router.map("routingexample://present/secret", 169 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil), 170 | style: .inNavigationController(.present(animated: true))) 171 | 172 | router.dispose(of: routeUUID) 173 | ``` 174 | 175 | ### Callback Queues 176 | 177 | A queue may be passed to maps or proxies. This queue will be the queue that a *RouteHandler* or *ProxyHandler* closure is called back on. By default, maps that are used for view controller navigation are called back on the main queue. 178 | 179 | ```swift 180 | let callbackQueue = DispatchQueue(label: "Call Back Queue", attributes: []) 181 | router.map("routingexample://route", queue: callbackQueue) { _, _, _, completed in 182 | completed() 183 | } 184 | ``` 185 | 186 | ### Presentation Setup 187 | 188 | View controllers mapped to the router will have the opportunity to be informed of a opened route through either a closure or the *RoutingPresentationSetup* protocol. In either implementation, the view controller will have access to the parameters passed through the URL. An example of the closure approach is in the [Usage](#usage) section above. The protocol looks as follows. 189 | 190 | ```swift 191 | class LoginViewController: UIViewController, RoutingPresentationSetup { 192 | var callback: String? 193 | 194 | func setup(_ route: String, with parameters: Parameters, passing any: Any?) { 195 | if let callbackURL = parameters["callback"] { 196 | self.callback = callbackURL 197 | } 198 | 199 | if let date = any as? NSDate { 200 | self.passedDate = date 201 | } 202 | } 203 | } 204 | ``` 205 | 206 | ### Presentation Styles 207 | 208 | ```swift 209 | indirect public enum PresentationStyle { 210 | case show 211 | case showDetail 212 | case present(animated: Bool) 213 | case push(animated: Bool) 214 | case custom(custom: (presenting: UIViewController, presented: UIViewController, completed: Routing.Completed) -> Void) 215 | case inNavigationController(Routing.PresentationStyle) 216 | } 217 | ``` 218 | 219 | The above presentation styles are made available. The recursive *.InNavigationController(PresentationStyle)* enumeration will result in the view controller being wrapped in a navigation controller before being presented in whatever fashion. There is also the ability to provide custom presentation styles. 220 | 221 | ### View Controller Sources 222 | 223 | The following view controller sources are utilized. 224 | 225 | ```swift 226 | public enum ControllerSource { 227 | case storyboard(storyboard: String, identifier: String, bundle: NSBundle?) 228 | case nib(controller: UIViewController.Type, name: String?, bundle: NSBundle?) 229 | case provided(() -> UIViewController) 230 | } 231 | ``` 232 | 233 | ### Presentation Extensions 234 | 235 | The following has been extended to allow for a completion closure to be passed in. 236 | 237 | ```swift 238 | extension UIViewController { 239 | public func showViewController(vc: UIViewController, sender: AnyObject?, completion: Routing.Completed) 240 | public func showDetailViewController(vc: UIViewController, sender: AnyObject?, completion: Routing.Completed) 241 | } 242 | 243 | extension UINavigationController { 244 | public func pushViewController(vc: UIViewController, animated: Bool, completion: Routing.Completed) 245 | public func popViewControllerAnimated(animated: Bool, completion: Routing.Completed) -> UIViewController? 246 | public func popToViewControllerAnimated(viewController: UIViewController, animated: Bool, completion: Routing.Completed) -> [UIViewController]? 247 | public func popToRootViewControllerAnimated(animated: Bool, completion: Routing.Completed) -> [UIViewController]? 248 | } 249 | ``` 250 | -------------------------------------------------------------------------------- /Routing.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Routing" 3 | s.version = "1.4.0" 4 | s.summary = "A Swift router implementation" 5 | s.description = <<-DESC 6 | Routing allows for routing URLs matched by string patterns to associated closures. 7 | DESC 8 | s.homepage = "https://github.com/jjgp/Routing" 9 | s.license = { :type => "MIT", :file => "LICENSE" } 10 | s.author = { "Jason Prasad" => "jasongprasad@gmail.com" } 11 | s.source = { :git => "https://github.com/jjgp/Routing.git", :tag => s.version.to_s } 12 | s.ios.deployment_target = '10.0' 13 | s.osx.deployment_target = '10.11' 14 | s.tvos.deployment_target = '12.2' 15 | s.watchos.deployment_target = '5.2' 16 | s.frameworks = 'Foundation' 17 | s.ios.framework = 'UIKit', 'QuartzCore' 18 | s.source_files = 'Source/Ro*.swift' 19 | s.ios.source_files = 'Source/iOS.swift' 20 | end 21 | -------------------------------------------------------------------------------- /Routing.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 92FCFA051BB963CD00DF05C3 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 92FCFA041BB963CD00DF05C3 /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 92FCFA0C1BB963CD00DF05C3 /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92FCFA011BB963CD00DF05C3 /* Routing.framework */; }; 12 | 92FCFA1C1BB9658D00DF05C3 /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; }; 13 | D24501A21D146B2F006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; }; 14 | D24501A31D14D1AF006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; }; 15 | D24501A41D14D1B0006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; }; 16 | D24501A51D14D1B1006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; }; 17 | D253AA411D8DE2C300F35644 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253AA401D8DE2C300F35644 /* RoutingTests.swift */; }; 18 | D253AA421D8DE2C300F35644 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253AA401D8DE2C300F35644 /* RoutingTests.swift */; }; 19 | D253AA431D8DE2D700F35644 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253AA401D8DE2C300F35644 /* RoutingTests.swift */; }; 20 | D273364E1CFE78980004A92A /* iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = D273364D1CFE78970004A92A /* iOS.swift */; }; 21 | D295F41F1C6C2612003CAC2F /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D295F4151C6C2612003CAC2F /* Routing.framework */; }; 22 | D295F43B1C6C265D003CAC2F /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D295F4311C6C265D003CAC2F /* Routing.framework */; }; 23 | D295F44A1C6C27B1003CAC2F /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; }; 24 | D295F44B1C6C27B2003CAC2F /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; }; 25 | D295F44C1C6C27B4003CAC2F /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; }; 26 | D2A0FCF51D652D5E007BF1F8 /* SecretViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0FCF41D652D5E007BF1F8 /* SecretViewController.swift */; }; 27 | D2B653961D5799B300A38633 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B653951D5799B300A38633 /* HomeViewController.swift */; }; 28 | D2B6539A1D57AB8C00A38633 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B653991D57AB8C00A38633 /* LoginViewController.swift */; }; 29 | D2B6539C1D57ACE300A38633 /* PrivilegedInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B6539B1D57ACE300A38633 /* PrivilegedInfoViewController.swift */; }; 30 | D2B6539E1D57AE1200A38633 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B6539D1D57AE1200A38633 /* SettingsViewController.swift */; }; 31 | D2FCC6081D5593D5005C474B /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92FCFA011BB963CD00DF05C3 /* Routing.framework */; }; 32 | D2FCC6091D5593D5005C474B /* Routing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 92FCFA011BB963CD00DF05C3 /* Routing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 33 | D2FCC61B1D5593F0005C474B /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FCC60D1D5593F0005C474B /* AppRoutes.swift */; }; 34 | D2FCC61C1D5593F0005C474B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FCC60E1D5593F0005C474B /* AppDelegate.swift */; }; 35 | D2FCC6231D5593F0005C474B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2FCC6161D5593F0005C474B /* Main.storyboard */; }; 36 | D2FCC6241D5593F0005C474B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2FCC6171D5593F0005C474B /* Assets.xcassets */; }; 37 | D2FCC6251D5593F0005C474B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2FCC6191D5593F0005C474B /* LaunchScreen.storyboard */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXContainerItemProxy section */ 41 | 92FCFA0D1BB963CD00DF05C3 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = 92FCFA001BB963CD00DF05C3; 46 | remoteInfo = Routing; 47 | }; 48 | D24099D11D55958B009A3FD4 /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */; 51 | proxyType = 1; 52 | remoteGlobalIDString = D2FCC5F51D5593BF005C474B; 53 | remoteInfo = Example; 54 | }; 55 | D295F4201C6C2612003CAC2F /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */; 58 | proxyType = 1; 59 | remoteGlobalIDString = D295F4141C6C2612003CAC2F; 60 | remoteInfo = "Routing tvOS"; 61 | }; 62 | D295F43C1C6C265D003CAC2F /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */; 65 | proxyType = 1; 66 | remoteGlobalIDString = D295F4301C6C265D003CAC2F; 67 | remoteInfo = "Routing OSX"; 68 | }; 69 | D2FCC60A1D5593D5005C474B /* PBXContainerItemProxy */ = { 70 | isa = PBXContainerItemProxy; 71 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */; 72 | proxyType = 1; 73 | remoteGlobalIDString = 92FCFA001BB963CD00DF05C3; 74 | remoteInfo = "Routing iOS"; 75 | }; 76 | /* End PBXContainerItemProxy section */ 77 | 78 | /* Begin PBXCopyFilesBuildPhase section */ 79 | D2FCC60C1D5593D5005C474B /* Embed Frameworks */ = { 80 | isa = PBXCopyFilesBuildPhase; 81 | buildActionMask = 2147483647; 82 | dstPath = ""; 83 | dstSubfolderSpec = 10; 84 | files = ( 85 | D2FCC6091D5593D5005C474B /* Routing.framework in Embed Frameworks */, 86 | ); 87 | name = "Embed Frameworks"; 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXCopyFilesBuildPhase section */ 91 | 92 | /* Begin PBXFileReference section */ 93 | 92FCFA011BB963CD00DF05C3 /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | 92FCFA041BB963CD00DF05C3 /* Routing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Routing.h; sourceTree = ""; }; 95 | 92FCFA061BB963CD00DF05C3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 96 | 92FCFA0B1BB963CD00DF05C3 /* Routing iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Routing iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 97 | 92FCFA121BB963CD00DF05C3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 98 | 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routing.swift; sourceTree = ""; }; 99 | D2204C7F1C8FE1B400ABA4E5 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 100 | D24501A11D146B2F006BBDC8 /* Routable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routable.swift; sourceTree = ""; }; 101 | D253AA401D8DE2C300F35644 /* RoutingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingTests.swift; sourceTree = ""; }; 102 | D273364D1CFE78970004A92A /* iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iOS.swift; sourceTree = ""; }; 103 | D295F4081C6C25ED003CAC2F /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 104 | D295F4151C6C2612003CAC2F /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 105 | D295F41E1C6C2612003CAC2F /* Routing tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Routing tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 106 | D295F4311C6C265D003CAC2F /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 107 | D295F43A1C6C265D003CAC2F /* Routing OSX Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Routing OSX Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 108 | D2A0FCF41D652D5E007BF1F8 /* SecretViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretViewController.swift; sourceTree = ""; }; 109 | D2B653951D5799B300A38633 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 110 | D2B653991D57AB8C00A38633 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 111 | D2B6539B1D57ACE300A38633 /* PrivilegedInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivilegedInfoViewController.swift; sourceTree = ""; }; 112 | D2B6539D1D57AE1200A38633 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 113 | D2FCC5F61D5593BF005C474B /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 114 | D2FCC60D1D5593F0005C474B /* AppRoutes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; 115 | D2FCC60E1D5593F0005C474B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 116 | D2FCC6151D5593F0005C474B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 117 | D2FCC6171D5593F0005C474B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 118 | D2FCC6181D5593F0005C474B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 119 | D2FCC61A1D5593F0005C474B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 120 | /* End PBXFileReference section */ 121 | 122 | /* Begin PBXFrameworksBuildPhase section */ 123 | 92FCF9FD1BB963CD00DF05C3 /* Frameworks */ = { 124 | isa = PBXFrameworksBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | 92FCFA081BB963CD00DF05C3 /* Frameworks */ = { 131 | isa = PBXFrameworksBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | 92FCFA0C1BB963CD00DF05C3 /* Routing.framework in Frameworks */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | D295F4041C6C25ED003CAC2F /* Frameworks */ = { 139 | isa = PBXFrameworksBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | D295F4111C6C2612003CAC2F /* Frameworks */ = { 146 | isa = PBXFrameworksBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | D295F41B1C6C2612003CAC2F /* Frameworks */ = { 153 | isa = PBXFrameworksBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | D295F41F1C6C2612003CAC2F /* Routing.framework in Frameworks */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | D295F42D1C6C265D003CAC2F /* Frameworks */ = { 161 | isa = PBXFrameworksBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | D295F4371C6C265D003CAC2F /* Frameworks */ = { 168 | isa = PBXFrameworksBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | D295F43B1C6C265D003CAC2F /* Routing.framework in Frameworks */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | D2FCC5F31D5593BF005C474B /* Frameworks */ = { 176 | isa = PBXFrameworksBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | D2FCC6081D5593D5005C474B /* Routing.framework in Frameworks */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXFrameworksBuildPhase section */ 184 | 185 | /* Begin PBXGroup section */ 186 | 92FCF9F71BB963CD00DF05C3 = { 187 | isa = PBXGroup; 188 | children = ( 189 | D2F5D0591C570ED600317401 /* Frameworks */, 190 | D2FCC5F71D5593BF005C474B /* Example */, 191 | 92FCFA031BB963CD00DF05C3 /* Source */, 192 | 92FCFA0F1BB963CD00DF05C3 /* Tests */, 193 | 92FCFA021BB963CD00DF05C3 /* Products */, 194 | ); 195 | sourceTree = ""; 196 | }; 197 | 92FCFA021BB963CD00DF05C3 /* Products */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 92FCFA011BB963CD00DF05C3 /* Routing.framework */, 201 | 92FCFA0B1BB963CD00DF05C3 /* Routing iOS Tests.xctest */, 202 | D295F4081C6C25ED003CAC2F /* Routing.framework */, 203 | D295F4151C6C2612003CAC2F /* Routing.framework */, 204 | D295F41E1C6C2612003CAC2F /* Routing tvOS Tests.xctest */, 205 | D295F4311C6C265D003CAC2F /* Routing.framework */, 206 | D295F43A1C6C265D003CAC2F /* Routing OSX Tests.xctest */, 207 | D2FCC5F61D5593BF005C474B /* Example.app */, 208 | ); 209 | name = Products; 210 | sourceTree = ""; 211 | }; 212 | 92FCFA031BB963CD00DF05C3 /* Source */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 92FCFA041BB963CD00DF05C3 /* Routing.h */, 216 | D273364D1CFE78970004A92A /* iOS.swift */, 217 | D24501A11D146B2F006BBDC8 /* Routable.swift */, 218 | 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */, 219 | 92FCFA061BB963CD00DF05C3 /* Info.plist */, 220 | ); 221 | path = Source; 222 | sourceTree = ""; 223 | }; 224 | 92FCFA0F1BB963CD00DF05C3 /* Tests */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | D253AA401D8DE2C300F35644 /* RoutingTests.swift */, 228 | 92FCFA121BB963CD00DF05C3 /* Info.plist */, 229 | ); 230 | path = Tests; 231 | sourceTree = ""; 232 | }; 233 | D2F5D0591C570ED600317401 /* Frameworks */ = { 234 | isa = PBXGroup; 235 | children = ( 236 | D2204C7F1C8FE1B400ABA4E5 /* UIKit.framework */, 237 | ); 238 | name = Frameworks; 239 | sourceTree = ""; 240 | }; 241 | D2FCC5F71D5593BF005C474B /* Example */ = { 242 | isa = PBXGroup; 243 | children = ( 244 | D2FCC60E1D5593F0005C474B /* AppDelegate.swift */, 245 | D2FCC60D1D5593F0005C474B /* AppRoutes.swift */, 246 | D2B653951D5799B300A38633 /* HomeViewController.swift */, 247 | D2B653991D57AB8C00A38633 /* LoginViewController.swift */, 248 | D2B6539B1D57ACE300A38633 /* PrivilegedInfoViewController.swift */, 249 | D2B6539D1D57AE1200A38633 /* SettingsViewController.swift */, 250 | D2A0FCF41D652D5E007BF1F8 /* SecretViewController.swift */, 251 | D2FCC6161D5593F0005C474B /* Main.storyboard */, 252 | D2FCC6171D5593F0005C474B /* Assets.xcassets */, 253 | D2FCC6191D5593F0005C474B /* LaunchScreen.storyboard */, 254 | D2FCC61A1D5593F0005C474B /* Info.plist */, 255 | ); 256 | path = Example; 257 | sourceTree = ""; 258 | }; 259 | /* End PBXGroup section */ 260 | 261 | /* Begin PBXHeadersBuildPhase section */ 262 | 92FCF9FE1BB963CD00DF05C3 /* Headers */ = { 263 | isa = PBXHeadersBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 92FCFA051BB963CD00DF05C3 /* Routing.h in Headers */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | D295F4051C6C25ED003CAC2F /* Headers */ = { 271 | isa = PBXHeadersBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | D295F4121C6C2612003CAC2F /* Headers */ = { 278 | isa = PBXHeadersBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | D295F42E1C6C265D003CAC2F /* Headers */ = { 285 | isa = PBXHeadersBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | /* End PBXHeadersBuildPhase section */ 292 | 293 | /* Begin PBXNativeTarget section */ 294 | 92FCFA001BB963CD00DF05C3 /* Routing iOS */ = { 295 | isa = PBXNativeTarget; 296 | buildConfigurationList = 92FCFA151BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS" */; 297 | buildPhases = ( 298 | 92FCF9FC1BB963CD00DF05C3 /* Sources */, 299 | 92FCF9FD1BB963CD00DF05C3 /* Frameworks */, 300 | 92FCF9FE1BB963CD00DF05C3 /* Headers */, 301 | 92FCF9FF1BB963CD00DF05C3 /* Resources */, 302 | ); 303 | buildRules = ( 304 | ); 305 | dependencies = ( 306 | ); 307 | name = "Routing iOS"; 308 | productName = Routing; 309 | productReference = 92FCFA011BB963CD00DF05C3 /* Routing.framework */; 310 | productType = "com.apple.product-type.framework"; 311 | }; 312 | 92FCFA0A1BB963CD00DF05C3 /* Routing iOS Tests */ = { 313 | isa = PBXNativeTarget; 314 | buildConfigurationList = 92FCFA181BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS Tests" */; 315 | buildPhases = ( 316 | 92FCFA071BB963CD00DF05C3 /* Sources */, 317 | 92FCFA081BB963CD00DF05C3 /* Frameworks */, 318 | 92FCFA091BB963CD00DF05C3 /* Resources */, 319 | ); 320 | buildRules = ( 321 | ); 322 | dependencies = ( 323 | 92FCFA0E1BB963CD00DF05C3 /* PBXTargetDependency */, 324 | D24099D21D55958B009A3FD4 /* PBXTargetDependency */, 325 | ); 326 | name = "Routing iOS Tests"; 327 | productName = RoutingTests; 328 | productReference = 92FCFA0B1BB963CD00DF05C3 /* Routing iOS Tests.xctest */; 329 | productType = "com.apple.product-type.bundle.unit-test"; 330 | }; 331 | D295F4071C6C25ED003CAC2F /* Routing watchOS */ = { 332 | isa = PBXNativeTarget; 333 | buildConfigurationList = D295F40F1C6C25ED003CAC2F /* Build configuration list for PBXNativeTarget "Routing watchOS" */; 334 | buildPhases = ( 335 | D295F4031C6C25ED003CAC2F /* Sources */, 336 | D295F4041C6C25ED003CAC2F /* Frameworks */, 337 | D295F4051C6C25ED003CAC2F /* Headers */, 338 | D295F4061C6C25ED003CAC2F /* Resources */, 339 | ); 340 | buildRules = ( 341 | ); 342 | dependencies = ( 343 | ); 344 | name = "Routing watchOS"; 345 | productName = "Routing watchOS"; 346 | productReference = D295F4081C6C25ED003CAC2F /* Routing.framework */; 347 | productType = "com.apple.product-type.framework"; 348 | }; 349 | D295F4141C6C2612003CAC2F /* Routing tvOS */ = { 350 | isa = PBXNativeTarget; 351 | buildConfigurationList = D295F4261C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS" */; 352 | buildPhases = ( 353 | D295F4101C6C2612003CAC2F /* Sources */, 354 | D295F4111C6C2612003CAC2F /* Frameworks */, 355 | D295F4121C6C2612003CAC2F /* Headers */, 356 | D295F4131C6C2612003CAC2F /* Resources */, 357 | ); 358 | buildRules = ( 359 | ); 360 | dependencies = ( 361 | ); 362 | name = "Routing tvOS"; 363 | productName = "Routing tvOS"; 364 | productReference = D295F4151C6C2612003CAC2F /* Routing.framework */; 365 | productType = "com.apple.product-type.framework"; 366 | }; 367 | D295F41D1C6C2612003CAC2F /* Routing tvOS Tests */ = { 368 | isa = PBXNativeTarget; 369 | buildConfigurationList = D295F4291C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS Tests" */; 370 | buildPhases = ( 371 | D295F41A1C6C2612003CAC2F /* Sources */, 372 | D295F41B1C6C2612003CAC2F /* Frameworks */, 373 | D295F41C1C6C2612003CAC2F /* Resources */, 374 | ); 375 | buildRules = ( 376 | ); 377 | dependencies = ( 378 | D295F4211C6C2612003CAC2F /* PBXTargetDependency */, 379 | ); 380 | name = "Routing tvOS Tests"; 381 | productName = "Routing tvOSTests"; 382 | productReference = D295F41E1C6C2612003CAC2F /* Routing tvOS Tests.xctest */; 383 | productType = "com.apple.product-type.bundle.unit-test"; 384 | }; 385 | D295F4301C6C265D003CAC2F /* Routing OSX */ = { 386 | isa = PBXNativeTarget; 387 | buildConfigurationList = D295F4421C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX" */; 388 | buildPhases = ( 389 | D295F42C1C6C265D003CAC2F /* Sources */, 390 | D295F42D1C6C265D003CAC2F /* Frameworks */, 391 | D295F42E1C6C265D003CAC2F /* Headers */, 392 | D295F42F1C6C265D003CAC2F /* Resources */, 393 | ); 394 | buildRules = ( 395 | ); 396 | dependencies = ( 397 | ); 398 | name = "Routing OSX"; 399 | productName = "Routing OSX"; 400 | productReference = D295F4311C6C265D003CAC2F /* Routing.framework */; 401 | productType = "com.apple.product-type.framework"; 402 | }; 403 | D295F4391C6C265D003CAC2F /* Routing OSX Tests */ = { 404 | isa = PBXNativeTarget; 405 | buildConfigurationList = D295F4451C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX Tests" */; 406 | buildPhases = ( 407 | D295F4361C6C265D003CAC2F /* Sources */, 408 | D295F4371C6C265D003CAC2F /* Frameworks */, 409 | D295F4381C6C265D003CAC2F /* Resources */, 410 | ); 411 | buildRules = ( 412 | ); 413 | dependencies = ( 414 | D295F43D1C6C265D003CAC2F /* PBXTargetDependency */, 415 | ); 416 | name = "Routing OSX Tests"; 417 | productName = "Routing OSXTests"; 418 | productReference = D295F43A1C6C265D003CAC2F /* Routing OSX Tests.xctest */; 419 | productType = "com.apple.product-type.bundle.unit-test"; 420 | }; 421 | D2FCC5F51D5593BF005C474B /* Example */ = { 422 | isa = PBXNativeTarget; 423 | buildConfigurationList = D2FCC6071D5593BF005C474B /* Build configuration list for PBXNativeTarget "Example" */; 424 | buildPhases = ( 425 | D2FCC5F21D5593BF005C474B /* Sources */, 426 | D2FCC5F31D5593BF005C474B /* Frameworks */, 427 | D2FCC5F41D5593BF005C474B /* Resources */, 428 | D2FCC60C1D5593D5005C474B /* Embed Frameworks */, 429 | ); 430 | buildRules = ( 431 | ); 432 | dependencies = ( 433 | D2FCC60B1D5593D5005C474B /* PBXTargetDependency */, 434 | ); 435 | name = Example; 436 | productName = Example; 437 | productReference = D2FCC5F61D5593BF005C474B /* Example.app */; 438 | productType = "com.apple.product-type.application"; 439 | }; 440 | /* End PBXNativeTarget section */ 441 | 442 | /* Begin PBXProject section */ 443 | 92FCF9F81BB963CD00DF05C3 /* Project object */ = { 444 | isa = PBXProject; 445 | attributes = { 446 | LastSwiftUpdateCheck = 0730; 447 | LastUpgradeCheck = 1020; 448 | ORGANIZATIONNAME = Routing; 449 | TargetAttributes = { 450 | 92FCFA001BB963CD00DF05C3 = { 451 | CreatedOnToolsVersion = 7.0; 452 | LastSwiftMigration = 0800; 453 | }; 454 | 92FCFA0A1BB963CD00DF05C3 = { 455 | CreatedOnToolsVersion = 7.0; 456 | LastSwiftMigration = 0800; 457 | TestTargetID = D2FCC5F51D5593BF005C474B; 458 | }; 459 | D295F4071C6C25ED003CAC2F = { 460 | CreatedOnToolsVersion = 7.2.1; 461 | LastSwiftMigration = 0800; 462 | }; 463 | D295F4141C6C2612003CAC2F = { 464 | CreatedOnToolsVersion = 7.2.1; 465 | LastSwiftMigration = 0800; 466 | }; 467 | D295F41D1C6C2612003CAC2F = { 468 | CreatedOnToolsVersion = 7.2.1; 469 | LastSwiftMigration = 0800; 470 | }; 471 | D295F4301C6C265D003CAC2F = { 472 | CreatedOnToolsVersion = 7.2.1; 473 | LastSwiftMigration = 0800; 474 | }; 475 | D295F4391C6C265D003CAC2F = { 476 | CreatedOnToolsVersion = 7.2.1; 477 | LastSwiftMigration = 0800; 478 | }; 479 | D2FCC5F51D5593BF005C474B = { 480 | CreatedOnToolsVersion = 7.3.1; 481 | LastSwiftMigration = 0800; 482 | }; 483 | }; 484 | }; 485 | buildConfigurationList = 92FCF9FB1BB963CD00DF05C3 /* Build configuration list for PBXProject "Routing" */; 486 | compatibilityVersion = "Xcode 3.2"; 487 | developmentRegion = en; 488 | hasScannedForEncodings = 0; 489 | knownRegions = ( 490 | en, 491 | Base, 492 | ); 493 | mainGroup = 92FCF9F71BB963CD00DF05C3; 494 | productRefGroup = 92FCFA021BB963CD00DF05C3 /* Products */; 495 | projectDirPath = ""; 496 | projectRoot = ""; 497 | targets = ( 498 | D2FCC5F51D5593BF005C474B /* Example */, 499 | 92FCFA001BB963CD00DF05C3 /* Routing iOS */, 500 | 92FCFA0A1BB963CD00DF05C3 /* Routing iOS Tests */, 501 | D295F4301C6C265D003CAC2F /* Routing OSX */, 502 | D295F4391C6C265D003CAC2F /* Routing OSX Tests */, 503 | D295F4141C6C2612003CAC2F /* Routing tvOS */, 504 | D295F41D1C6C2612003CAC2F /* Routing tvOS Tests */, 505 | D295F4071C6C25ED003CAC2F /* Routing watchOS */, 506 | ); 507 | }; 508 | /* End PBXProject section */ 509 | 510 | /* Begin PBXResourcesBuildPhase section */ 511 | 92FCF9FF1BB963CD00DF05C3 /* Resources */ = { 512 | isa = PBXResourcesBuildPhase; 513 | buildActionMask = 2147483647; 514 | files = ( 515 | ); 516 | runOnlyForDeploymentPostprocessing = 0; 517 | }; 518 | 92FCFA091BB963CD00DF05C3 /* Resources */ = { 519 | isa = PBXResourcesBuildPhase; 520 | buildActionMask = 2147483647; 521 | files = ( 522 | ); 523 | runOnlyForDeploymentPostprocessing = 0; 524 | }; 525 | D295F4061C6C25ED003CAC2F /* Resources */ = { 526 | isa = PBXResourcesBuildPhase; 527 | buildActionMask = 2147483647; 528 | files = ( 529 | ); 530 | runOnlyForDeploymentPostprocessing = 0; 531 | }; 532 | D295F4131C6C2612003CAC2F /* Resources */ = { 533 | isa = PBXResourcesBuildPhase; 534 | buildActionMask = 2147483647; 535 | files = ( 536 | ); 537 | runOnlyForDeploymentPostprocessing = 0; 538 | }; 539 | D295F41C1C6C2612003CAC2F /* Resources */ = { 540 | isa = PBXResourcesBuildPhase; 541 | buildActionMask = 2147483647; 542 | files = ( 543 | ); 544 | runOnlyForDeploymentPostprocessing = 0; 545 | }; 546 | D295F42F1C6C265D003CAC2F /* Resources */ = { 547 | isa = PBXResourcesBuildPhase; 548 | buildActionMask = 2147483647; 549 | files = ( 550 | ); 551 | runOnlyForDeploymentPostprocessing = 0; 552 | }; 553 | D295F4381C6C265D003CAC2F /* Resources */ = { 554 | isa = PBXResourcesBuildPhase; 555 | buildActionMask = 2147483647; 556 | files = ( 557 | ); 558 | runOnlyForDeploymentPostprocessing = 0; 559 | }; 560 | D2FCC5F41D5593BF005C474B /* Resources */ = { 561 | isa = PBXResourcesBuildPhase; 562 | buildActionMask = 2147483647; 563 | files = ( 564 | D2FCC6251D5593F0005C474B /* LaunchScreen.storyboard in Resources */, 565 | D2FCC6241D5593F0005C474B /* Assets.xcassets in Resources */, 566 | D2FCC6231D5593F0005C474B /* Main.storyboard in Resources */, 567 | ); 568 | runOnlyForDeploymentPostprocessing = 0; 569 | }; 570 | /* End PBXResourcesBuildPhase section */ 571 | 572 | /* Begin PBXSourcesBuildPhase section */ 573 | 92FCF9FC1BB963CD00DF05C3 /* Sources */ = { 574 | isa = PBXSourcesBuildPhase; 575 | buildActionMask = 2147483647; 576 | files = ( 577 | D24501A21D146B2F006BBDC8 /* Routable.swift in Sources */, 578 | D273364E1CFE78980004A92A /* iOS.swift in Sources */, 579 | 92FCFA1C1BB9658D00DF05C3 /* Routing.swift in Sources */, 580 | ); 581 | runOnlyForDeploymentPostprocessing = 0; 582 | }; 583 | 92FCFA071BB963CD00DF05C3 /* Sources */ = { 584 | isa = PBXSourcesBuildPhase; 585 | buildActionMask = 2147483647; 586 | files = ( 587 | D253AA411D8DE2C300F35644 /* RoutingTests.swift in Sources */, 588 | ); 589 | runOnlyForDeploymentPostprocessing = 0; 590 | }; 591 | D295F4031C6C25ED003CAC2F /* Sources */ = { 592 | isa = PBXSourcesBuildPhase; 593 | buildActionMask = 2147483647; 594 | files = ( 595 | D295F44C1C6C27B4003CAC2F /* Routing.swift in Sources */, 596 | D24501A51D14D1B1006BBDC8 /* Routable.swift in Sources */, 597 | ); 598 | runOnlyForDeploymentPostprocessing = 0; 599 | }; 600 | D295F4101C6C2612003CAC2F /* Sources */ = { 601 | isa = PBXSourcesBuildPhase; 602 | buildActionMask = 2147483647; 603 | files = ( 604 | D295F44A1C6C27B1003CAC2F /* Routing.swift in Sources */, 605 | D24501A41D14D1B0006BBDC8 /* Routable.swift in Sources */, 606 | ); 607 | runOnlyForDeploymentPostprocessing = 0; 608 | }; 609 | D295F41A1C6C2612003CAC2F /* Sources */ = { 610 | isa = PBXSourcesBuildPhase; 611 | buildActionMask = 2147483647; 612 | files = ( 613 | D253AA431D8DE2D700F35644 /* RoutingTests.swift in Sources */, 614 | ); 615 | runOnlyForDeploymentPostprocessing = 0; 616 | }; 617 | D295F42C1C6C265D003CAC2F /* Sources */ = { 618 | isa = PBXSourcesBuildPhase; 619 | buildActionMask = 2147483647; 620 | files = ( 621 | D295F44B1C6C27B2003CAC2F /* Routing.swift in Sources */, 622 | D24501A31D14D1AF006BBDC8 /* Routable.swift in Sources */, 623 | ); 624 | runOnlyForDeploymentPostprocessing = 0; 625 | }; 626 | D295F4361C6C265D003CAC2F /* Sources */ = { 627 | isa = PBXSourcesBuildPhase; 628 | buildActionMask = 2147483647; 629 | files = ( 630 | D253AA421D8DE2C300F35644 /* RoutingTests.swift in Sources */, 631 | ); 632 | runOnlyForDeploymentPostprocessing = 0; 633 | }; 634 | D2FCC5F21D5593BF005C474B /* Sources */ = { 635 | isa = PBXSourcesBuildPhase; 636 | buildActionMask = 2147483647; 637 | files = ( 638 | D2B6539C1D57ACE300A38633 /* PrivilegedInfoViewController.swift in Sources */, 639 | D2A0FCF51D652D5E007BF1F8 /* SecretViewController.swift in Sources */, 640 | D2B6539A1D57AB8C00A38633 /* LoginViewController.swift in Sources */, 641 | D2FCC61C1D5593F0005C474B /* AppDelegate.swift in Sources */, 642 | D2FCC61B1D5593F0005C474B /* AppRoutes.swift in Sources */, 643 | D2B653961D5799B300A38633 /* HomeViewController.swift in Sources */, 644 | D2B6539E1D57AE1200A38633 /* SettingsViewController.swift in Sources */, 645 | ); 646 | runOnlyForDeploymentPostprocessing = 0; 647 | }; 648 | /* End PBXSourcesBuildPhase section */ 649 | 650 | /* Begin PBXTargetDependency section */ 651 | 92FCFA0E1BB963CD00DF05C3 /* PBXTargetDependency */ = { 652 | isa = PBXTargetDependency; 653 | target = 92FCFA001BB963CD00DF05C3 /* Routing iOS */; 654 | targetProxy = 92FCFA0D1BB963CD00DF05C3 /* PBXContainerItemProxy */; 655 | }; 656 | D24099D21D55958B009A3FD4 /* PBXTargetDependency */ = { 657 | isa = PBXTargetDependency; 658 | target = D2FCC5F51D5593BF005C474B /* Example */; 659 | targetProxy = D24099D11D55958B009A3FD4 /* PBXContainerItemProxy */; 660 | }; 661 | D295F4211C6C2612003CAC2F /* PBXTargetDependency */ = { 662 | isa = PBXTargetDependency; 663 | target = D295F4141C6C2612003CAC2F /* Routing tvOS */; 664 | targetProxy = D295F4201C6C2612003CAC2F /* PBXContainerItemProxy */; 665 | }; 666 | D295F43D1C6C265D003CAC2F /* PBXTargetDependency */ = { 667 | isa = PBXTargetDependency; 668 | target = D295F4301C6C265D003CAC2F /* Routing OSX */; 669 | targetProxy = D295F43C1C6C265D003CAC2F /* PBXContainerItemProxy */; 670 | }; 671 | D2FCC60B1D5593D5005C474B /* PBXTargetDependency */ = { 672 | isa = PBXTargetDependency; 673 | target = 92FCFA001BB963CD00DF05C3 /* Routing iOS */; 674 | targetProxy = D2FCC60A1D5593D5005C474B /* PBXContainerItemProxy */; 675 | }; 676 | /* End PBXTargetDependency section */ 677 | 678 | /* Begin PBXVariantGroup section */ 679 | D2FCC6161D5593F0005C474B /* Main.storyboard */ = { 680 | isa = PBXVariantGroup; 681 | children = ( 682 | D2FCC6151D5593F0005C474B /* Base */, 683 | ); 684 | name = Main.storyboard; 685 | sourceTree = ""; 686 | }; 687 | D2FCC6191D5593F0005C474B /* LaunchScreen.storyboard */ = { 688 | isa = PBXVariantGroup; 689 | children = ( 690 | D2FCC6181D5593F0005C474B /* Base */, 691 | ); 692 | name = LaunchScreen.storyboard; 693 | sourceTree = ""; 694 | }; 695 | /* End PBXVariantGroup section */ 696 | 697 | /* Begin XCBuildConfiguration section */ 698 | 92FCFA131BB963CD00DF05C3 /* Debug */ = { 699 | isa = XCBuildConfiguration; 700 | buildSettings = { 701 | ALWAYS_SEARCH_USER_PATHS = NO; 702 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 703 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 704 | CLANG_CXX_LIBRARY = "libc++"; 705 | CLANG_ENABLE_MODULES = YES; 706 | CLANG_ENABLE_OBJC_ARC = YES; 707 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 708 | CLANG_WARN_BOOL_CONVERSION = YES; 709 | CLANG_WARN_COMMA = YES; 710 | CLANG_WARN_CONSTANT_CONVERSION = YES; 711 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 712 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 713 | CLANG_WARN_EMPTY_BODY = YES; 714 | CLANG_WARN_ENUM_CONVERSION = YES; 715 | CLANG_WARN_INFINITE_RECURSION = YES; 716 | CLANG_WARN_INT_CONVERSION = YES; 717 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 718 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 719 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 720 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 721 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 722 | CLANG_WARN_STRICT_PROTOTYPES = YES; 723 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 724 | CLANG_WARN_UNREACHABLE_CODE = YES; 725 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 726 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 727 | COPY_PHASE_STRIP = NO; 728 | CURRENT_PROJECT_VERSION = 1; 729 | DEBUG_INFORMATION_FORMAT = dwarf; 730 | ENABLE_STRICT_OBJC_MSGSEND = YES; 731 | ENABLE_TESTABILITY = YES; 732 | GCC_C_LANGUAGE_STANDARD = gnu99; 733 | GCC_DYNAMIC_NO_PIC = NO; 734 | GCC_NO_COMMON_BLOCKS = YES; 735 | GCC_OPTIMIZATION_LEVEL = 0; 736 | GCC_PREPROCESSOR_DEFINITIONS = ( 737 | "DEBUG=1", 738 | "$(inherited)", 739 | ); 740 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 741 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 742 | GCC_WARN_UNDECLARED_SELECTOR = YES; 743 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 744 | GCC_WARN_UNUSED_FUNCTION = YES; 745 | GCC_WARN_UNUSED_VARIABLE = YES; 746 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 747 | MACOSX_DEPLOYMENT_TARGET = 10.11; 748 | MTL_ENABLE_DEBUG_INFO = YES; 749 | ONLY_ACTIVE_ARCH = YES; 750 | SDKROOT = iphoneos; 751 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 752 | SWIFT_VERSION = 5.0; 753 | TARGETED_DEVICE_FAMILY = "1,2"; 754 | VERSIONING_SYSTEM = "apple-generic"; 755 | VERSION_INFO_PREFIX = ""; 756 | }; 757 | name = Debug; 758 | }; 759 | 92FCFA141BB963CD00DF05C3 /* Release */ = { 760 | isa = XCBuildConfiguration; 761 | buildSettings = { 762 | ALWAYS_SEARCH_USER_PATHS = NO; 763 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 764 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 765 | CLANG_CXX_LIBRARY = "libc++"; 766 | CLANG_ENABLE_MODULES = YES; 767 | CLANG_ENABLE_OBJC_ARC = YES; 768 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 769 | CLANG_WARN_BOOL_CONVERSION = YES; 770 | CLANG_WARN_COMMA = YES; 771 | CLANG_WARN_CONSTANT_CONVERSION = YES; 772 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 773 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 774 | CLANG_WARN_EMPTY_BODY = YES; 775 | CLANG_WARN_ENUM_CONVERSION = YES; 776 | CLANG_WARN_INFINITE_RECURSION = YES; 777 | CLANG_WARN_INT_CONVERSION = YES; 778 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 779 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 780 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 781 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 782 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 783 | CLANG_WARN_STRICT_PROTOTYPES = YES; 784 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 785 | CLANG_WARN_UNREACHABLE_CODE = YES; 786 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 787 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 788 | COPY_PHASE_STRIP = NO; 789 | CURRENT_PROJECT_VERSION = 1; 790 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 791 | ENABLE_NS_ASSERTIONS = NO; 792 | ENABLE_STRICT_OBJC_MSGSEND = YES; 793 | GCC_C_LANGUAGE_STANDARD = gnu99; 794 | GCC_NO_COMMON_BLOCKS = YES; 795 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 796 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 797 | GCC_WARN_UNDECLARED_SELECTOR = YES; 798 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 799 | GCC_WARN_UNUSED_FUNCTION = YES; 800 | GCC_WARN_UNUSED_VARIABLE = YES; 801 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 802 | MACOSX_DEPLOYMENT_TARGET = 10.11; 803 | MTL_ENABLE_DEBUG_INFO = NO; 804 | SDKROOT = iphoneos; 805 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 806 | SWIFT_VERSION = 5.0; 807 | TARGETED_DEVICE_FAMILY = "1,2"; 808 | VALIDATE_PRODUCT = YES; 809 | VERSIONING_SYSTEM = "apple-generic"; 810 | VERSION_INFO_PREFIX = ""; 811 | }; 812 | name = Release; 813 | }; 814 | 92FCFA161BB963CD00DF05C3 /* Debug */ = { 815 | isa = XCBuildConfiguration; 816 | buildSettings = { 817 | CLANG_ENABLE_MODULES = YES; 818 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 819 | DEFINES_MODULE = YES; 820 | DYLIB_COMPATIBILITY_VERSION = 1; 821 | DYLIB_CURRENT_VERSION = 1; 822 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 823 | INFOPLIST_FILE = Source/Info.plist; 824 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 825 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 826 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 827 | PRODUCT_NAME = Routing; 828 | SKIP_INSTALL = YES; 829 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 830 | }; 831 | name = Debug; 832 | }; 833 | 92FCFA171BB963CD00DF05C3 /* Release */ = { 834 | isa = XCBuildConfiguration; 835 | buildSettings = { 836 | CLANG_ENABLE_MODULES = YES; 837 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 838 | DEFINES_MODULE = YES; 839 | DYLIB_COMPATIBILITY_VERSION = 1; 840 | DYLIB_CURRENT_VERSION = 1; 841 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 842 | INFOPLIST_FILE = Source/Info.plist; 843 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 844 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 845 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 846 | PRODUCT_NAME = Routing; 847 | SKIP_INSTALL = YES; 848 | }; 849 | name = Release; 850 | }; 851 | 92FCFA191BB963CD00DF05C3 /* Debug */ = { 852 | isa = XCBuildConfiguration; 853 | buildSettings = { 854 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 855 | INFOPLIST_FILE = Tests/Info.plist; 856 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 857 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 858 | PRODUCT_NAME = "$(TARGET_NAME)"; 859 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 860 | }; 861 | name = Debug; 862 | }; 863 | 92FCFA1A1BB963CD00DF05C3 /* Release */ = { 864 | isa = XCBuildConfiguration; 865 | buildSettings = { 866 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 867 | INFOPLIST_FILE = Tests/Info.plist; 868 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 869 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 870 | PRODUCT_NAME = "$(TARGET_NAME)"; 871 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 872 | }; 873 | name = Release; 874 | }; 875 | D295F40D1C6C25ED003CAC2F /* Debug */ = { 876 | isa = XCBuildConfiguration; 877 | buildSettings = { 878 | APPLICATION_EXTENSION_API_ONLY = YES; 879 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 880 | DEFINES_MODULE = YES; 881 | DYLIB_COMPATIBILITY_VERSION = 1; 882 | DYLIB_CURRENT_VERSION = 1; 883 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 884 | INFOPLIST_FILE = Source/Info.plist; 885 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 886 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 887 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 888 | PRODUCT_NAME = Routing; 889 | SDKROOT = watchos; 890 | SKIP_INSTALL = YES; 891 | TARGETED_DEVICE_FAMILY = 4; 892 | }; 893 | name = Debug; 894 | }; 895 | D295F40E1C6C25ED003CAC2F /* Release */ = { 896 | isa = XCBuildConfiguration; 897 | buildSettings = { 898 | APPLICATION_EXTENSION_API_ONLY = YES; 899 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 900 | DEFINES_MODULE = YES; 901 | DYLIB_COMPATIBILITY_VERSION = 1; 902 | DYLIB_CURRENT_VERSION = 1; 903 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 904 | INFOPLIST_FILE = Source/Info.plist; 905 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 906 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 907 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 908 | PRODUCT_NAME = Routing; 909 | SDKROOT = watchos; 910 | SKIP_INSTALL = YES; 911 | TARGETED_DEVICE_FAMILY = 4; 912 | }; 913 | name = Release; 914 | }; 915 | D295F4271C6C2612003CAC2F /* Debug */ = { 916 | isa = XCBuildConfiguration; 917 | buildSettings = { 918 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 919 | DEFINES_MODULE = YES; 920 | DYLIB_COMPATIBILITY_VERSION = 1; 921 | DYLIB_CURRENT_VERSION = 1; 922 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 923 | INFOPLIST_FILE = Source/Info.plist; 924 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 925 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 926 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 927 | PRODUCT_NAME = Routing; 928 | SDKROOT = appletvos; 929 | SKIP_INSTALL = YES; 930 | TARGETED_DEVICE_FAMILY = 3; 931 | }; 932 | name = Debug; 933 | }; 934 | D295F4281C6C2612003CAC2F /* Release */ = { 935 | isa = XCBuildConfiguration; 936 | buildSettings = { 937 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 938 | DEFINES_MODULE = YES; 939 | DYLIB_COMPATIBILITY_VERSION = 1; 940 | DYLIB_CURRENT_VERSION = 1; 941 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 942 | INFOPLIST_FILE = Source/Info.plist; 943 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 944 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 945 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 946 | PRODUCT_NAME = Routing; 947 | SDKROOT = appletvos; 948 | SKIP_INSTALL = YES; 949 | TARGETED_DEVICE_FAMILY = 3; 950 | }; 951 | name = Release; 952 | }; 953 | D295F42A1C6C2612003CAC2F /* Debug */ = { 954 | isa = XCBuildConfiguration; 955 | buildSettings = { 956 | INFOPLIST_FILE = Tests/Info.plist; 957 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 958 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 959 | PRODUCT_NAME = "$(TARGET_NAME)"; 960 | SDKROOT = appletvos; 961 | }; 962 | name = Debug; 963 | }; 964 | D295F42B1C6C2612003CAC2F /* Release */ = { 965 | isa = XCBuildConfiguration; 966 | buildSettings = { 967 | INFOPLIST_FILE = Tests/Info.plist; 968 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 969 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 970 | PRODUCT_NAME = "$(TARGET_NAME)"; 971 | SDKROOT = appletvos; 972 | }; 973 | name = Release; 974 | }; 975 | D295F4431C6C265D003CAC2F /* Debug */ = { 976 | isa = XCBuildConfiguration; 977 | buildSettings = { 978 | CODE_SIGN_IDENTITY = ""; 979 | COMBINE_HIDPI_IMAGES = YES; 980 | DEFINES_MODULE = YES; 981 | DYLIB_COMPATIBILITY_VERSION = 1; 982 | DYLIB_CURRENT_VERSION = 1; 983 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 984 | FRAMEWORK_VERSION = A; 985 | INFOPLIST_FILE = Source/Info.plist; 986 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 987 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 988 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 989 | PRODUCT_NAME = Routing; 990 | SDKROOT = macosx; 991 | SKIP_INSTALL = YES; 992 | }; 993 | name = Debug; 994 | }; 995 | D295F4441C6C265D003CAC2F /* Release */ = { 996 | isa = XCBuildConfiguration; 997 | buildSettings = { 998 | CODE_SIGN_IDENTITY = ""; 999 | COMBINE_HIDPI_IMAGES = YES; 1000 | DEFINES_MODULE = YES; 1001 | DYLIB_COMPATIBILITY_VERSION = 1; 1002 | DYLIB_CURRENT_VERSION = 1; 1003 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1004 | FRAMEWORK_VERSION = A; 1005 | INFOPLIST_FILE = Source/Info.plist; 1006 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1007 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 1008 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 1009 | PRODUCT_NAME = Routing; 1010 | SDKROOT = macosx; 1011 | SKIP_INSTALL = YES; 1012 | }; 1013 | name = Release; 1014 | }; 1015 | D295F4461C6C265D003CAC2F /* Debug */ = { 1016 | isa = XCBuildConfiguration; 1017 | buildSettings = { 1018 | CODE_SIGN_IDENTITY = "-"; 1019 | COMBINE_HIDPI_IMAGES = YES; 1020 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 1021 | INFOPLIST_FILE = Tests/Info.plist; 1022 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1023 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 1024 | PRODUCT_NAME = "$(TARGET_NAME)"; 1025 | SDKROOT = macosx; 1026 | }; 1027 | name = Debug; 1028 | }; 1029 | D295F4471C6C265D003CAC2F /* Release */ = { 1030 | isa = XCBuildConfiguration; 1031 | buildSettings = { 1032 | CODE_SIGN_IDENTITY = "-"; 1033 | COMBINE_HIDPI_IMAGES = YES; 1034 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 1035 | INFOPLIST_FILE = Tests/Info.plist; 1036 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1037 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 1038 | PRODUCT_NAME = "$(TARGET_NAME)"; 1039 | SDKROOT = macosx; 1040 | }; 1041 | name = Release; 1042 | }; 1043 | D2FCC6051D5593BF005C474B /* Debug */ = { 1044 | isa = XCBuildConfiguration; 1045 | buildSettings = { 1046 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1047 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1048 | CLANG_ANALYZER_NONNULL = YES; 1049 | INFOPLIST_FILE = Example/Info.plist; 1050 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 1051 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 1052 | PRODUCT_NAME = "$(TARGET_NAME)"; 1053 | }; 1054 | name = Debug; 1055 | }; 1056 | D2FCC6061D5593BF005C474B /* Release */ = { 1057 | isa = XCBuildConfiguration; 1058 | buildSettings = { 1059 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1060 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1061 | CLANG_ANALYZER_NONNULL = YES; 1062 | INFOPLIST_FILE = Example/Info.plist; 1063 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 1064 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)"; 1065 | PRODUCT_NAME = "$(TARGET_NAME)"; 1066 | }; 1067 | name = Release; 1068 | }; 1069 | /* End XCBuildConfiguration section */ 1070 | 1071 | /* Begin XCConfigurationList section */ 1072 | 92FCF9FB1BB963CD00DF05C3 /* Build configuration list for PBXProject "Routing" */ = { 1073 | isa = XCConfigurationList; 1074 | buildConfigurations = ( 1075 | 92FCFA131BB963CD00DF05C3 /* Debug */, 1076 | 92FCFA141BB963CD00DF05C3 /* Release */, 1077 | ); 1078 | defaultConfigurationIsVisible = 0; 1079 | defaultConfigurationName = Release; 1080 | }; 1081 | 92FCFA151BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS" */ = { 1082 | isa = XCConfigurationList; 1083 | buildConfigurations = ( 1084 | 92FCFA161BB963CD00DF05C3 /* Debug */, 1085 | 92FCFA171BB963CD00DF05C3 /* Release */, 1086 | ); 1087 | defaultConfigurationIsVisible = 0; 1088 | defaultConfigurationName = Release; 1089 | }; 1090 | 92FCFA181BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS Tests" */ = { 1091 | isa = XCConfigurationList; 1092 | buildConfigurations = ( 1093 | 92FCFA191BB963CD00DF05C3 /* Debug */, 1094 | 92FCFA1A1BB963CD00DF05C3 /* Release */, 1095 | ); 1096 | defaultConfigurationIsVisible = 0; 1097 | defaultConfigurationName = Release; 1098 | }; 1099 | D295F40F1C6C25ED003CAC2F /* Build configuration list for PBXNativeTarget "Routing watchOS" */ = { 1100 | isa = XCConfigurationList; 1101 | buildConfigurations = ( 1102 | D295F40D1C6C25ED003CAC2F /* Debug */, 1103 | D295F40E1C6C25ED003CAC2F /* Release */, 1104 | ); 1105 | defaultConfigurationIsVisible = 0; 1106 | defaultConfigurationName = Release; 1107 | }; 1108 | D295F4261C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS" */ = { 1109 | isa = XCConfigurationList; 1110 | buildConfigurations = ( 1111 | D295F4271C6C2612003CAC2F /* Debug */, 1112 | D295F4281C6C2612003CAC2F /* Release */, 1113 | ); 1114 | defaultConfigurationIsVisible = 0; 1115 | defaultConfigurationName = Release; 1116 | }; 1117 | D295F4291C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS Tests" */ = { 1118 | isa = XCConfigurationList; 1119 | buildConfigurations = ( 1120 | D295F42A1C6C2612003CAC2F /* Debug */, 1121 | D295F42B1C6C2612003CAC2F /* Release */, 1122 | ); 1123 | defaultConfigurationIsVisible = 0; 1124 | defaultConfigurationName = Release; 1125 | }; 1126 | D295F4421C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX" */ = { 1127 | isa = XCConfigurationList; 1128 | buildConfigurations = ( 1129 | D295F4431C6C265D003CAC2F /* Debug */, 1130 | D295F4441C6C265D003CAC2F /* Release */, 1131 | ); 1132 | defaultConfigurationIsVisible = 0; 1133 | defaultConfigurationName = Release; 1134 | }; 1135 | D295F4451C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX Tests" */ = { 1136 | isa = XCConfigurationList; 1137 | buildConfigurations = ( 1138 | D295F4461C6C265D003CAC2F /* Debug */, 1139 | D295F4471C6C265D003CAC2F /* Release */, 1140 | ); 1141 | defaultConfigurationIsVisible = 0; 1142 | defaultConfigurationName = Release; 1143 | }; 1144 | D2FCC6071D5593BF005C474B /* Build configuration list for PBXNativeTarget "Example" */ = { 1145 | isa = XCConfigurationList; 1146 | buildConfigurations = ( 1147 | D2FCC6051D5593BF005C474B /* Debug */, 1148 | D2FCC6061D5593BF005C474B /* Release */, 1149 | ); 1150 | defaultConfigurationIsVisible = 0; 1151 | defaultConfigurationName = Release; 1152 | }; 1153 | /* End XCConfigurationList section */ 1154 | }; 1155 | rootObject = 92FCF9F81BB963CD00DF05C3 /* Project object */; 1156 | } 1157 | -------------------------------------------------------------------------------- /Routing.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Routing.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Routing.xcodeproj/xcshareddata/xcschemes/Routing OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Routing.xcodeproj/xcshareddata/xcschemes/Routing iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Routing.xcodeproj/xcshareddata/xcschemes/Routing tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Routing.xcodeproj/xcshareddata/xcschemes/Routing watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Routing.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Routing.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.4.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/Routable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol RouteOwner: class {} 4 | public typealias RouteUUID = String 5 | public typealias Parameters = [String: String] 6 | 7 | /** 8 | The closure type associated with #map 9 | 10 | - Parameter Parameters: Any query parameters or dynamic segments found in the URL 11 | - Parameter Any: Any data that could be passed with a routing 12 | - Parameter Completed: Must be called for Routing to continue processing other routes with #open 13 | */ 14 | 15 | public typealias RouteHandler = (String, Parameters, Any?, @escaping Completed) -> Void 16 | public typealias Completed = () -> Void 17 | 18 | /** 19 | The closure type associated with #proxy 20 | 21 | - Parameter String: The route being opened 22 | - Parameter Parameters: Any query parameters or dynamic segments found in the URL 23 | - Parameter Any: Any data that could be passed with a routing 24 | - Parameter Next: Must be called for Routing to continue processing. Calling #Next with 25 | nil arguments will continue executing other matching proxies. Calling #Next with non nil 26 | arguments will continue to process the route. 27 | */ 28 | 29 | public typealias ProxyHandler = (String, Parameters, Any?, @escaping Next) -> Void 30 | public typealias ProxyCommit = (route: String, parameters: Parameters, data: Any?) 31 | public typealias Next = (ProxyCommit?) -> Void 32 | 33 | internal typealias Route = Routable 34 | internal typealias Proxy = Routable 35 | 36 | internal struct Routable { 37 | let uuid = { UUID().uuidString }() 38 | let pattern: String 39 | let tags: [String] 40 | weak var owner: RouteOwner? 41 | let queue: DispatchQueue 42 | let handler: T 43 | let dynamicSegments: [String] 44 | 45 | init(_ pattern: String, tags: [String], owner: RouteOwner, queue: DispatchQueue, handler: T) { 46 | var pattern = pattern 47 | var dynamicSegments = [String]() 48 | let options: NSString.CompareOptions = [.regularExpression, .caseInsensitive] 49 | while let range = pattern.range(of: ":[a-zA-Z0-9-_]+", options: options) { 50 | dynamicSegments.append(String(pattern[pattern.index(range.lowerBound, offsetBy: 1).. Routing { 20 | get { 21 | let set = Set(tags) 22 | var sub: Routing! 23 | accessQueue.sync { 24 | sub = Routing(routes: self.routes.filter({ set.intersection($0.tags).isEmpty == false }), 25 | proxies: self.proxies.filter({ set.intersection($0.tags).isEmpty == false }), 26 | accessQueue: self.accessQueue, 27 | openQueue: self.openQueue, 28 | processingQueue: self.processQueue) 29 | } 30 | 31 | return sub 32 | } 33 | } 34 | 35 | public init() { 36 | accessQueue = DispatchQueue(label: "Routing Access Queue", attributes: []) 37 | openQueue = DispatchQueue(label: "Routing Open Queue", attributes: []) 38 | processQueue = DispatchQueue(label: "Routing Process Queue", attributes: []) 39 | } 40 | 41 | private init(routes: [Route], 42 | proxies: [Proxy], 43 | accessQueue: DispatchQueue, 44 | openQueue: DispatchQueue, 45 | processingQueue: DispatchQueue) { 46 | self.accessQueue = accessQueue 47 | self.openQueue = openQueue 48 | self.processQueue = processingQueue 49 | self.routes = routes 50 | self.proxies = proxies 51 | } 52 | 53 | /** 54 | Associates a closure to a string pattern. A Routing instance will execute the closure in the 55 | event of a matching URL using #open. Routing will only execute the first matching mapped route. 56 | This will be the last route added with #map. 57 | 58 | ```code 59 | let router = Routing() 60 | router.map("routing://route") { parameters, completed in 61 | completed() // Must call completed or the router will halt! 62 | } 63 | ``` 64 | 65 | - Parameter pattern: A String pattern 66 | - Parameter tag: A tag to reference when subscripting a Routing object 67 | - Parameter owner: The routes owner. If deallocated the route will be removed. 68 | - Parameter queue: A dispatch queue for the callback 69 | - Parameter handler: A MapHandler 70 | - Returns: The RouteUUID 71 | */ 72 | 73 | @discardableResult 74 | public func map(_ pattern: String, 75 | tags: [String] = [], 76 | queue: DispatchQueue = DispatchQueue.main, 77 | owner: RouteOwner? = nil, 78 | handler: @escaping RouteHandler) -> RouteUUID { 79 | let route = Route(pattern, tags: tags, owner: owner ?? self, queue: queue, handler: handler) 80 | accessQueue.async { 81 | self.routes.insert(route, at: 0) 82 | } 83 | 84 | return route.uuid 85 | } 86 | 87 | /** 88 | Associates a closure to a string pattern. A Routing instance will execute the closure in the 89 | event of a matching URL using #open. Routing will execute all proxies unless #next() is called 90 | with non nil arguments. 91 | 92 | ```code 93 | let router = Routing() 94 | router.proxy("routing://route") { route, parameters, next in 95 | next(route, parameters) // Must call next or the router will halt! 96 | /* alternatively, next(nil, nil) allowing additional proxies to execute */ 97 | } 98 | ``` 99 | 100 | - Parameter pattern: A String pattern 101 | - Parameter tag: A tag to reference when subscripting a Routing object 102 | - Parameter owner: The routes owner. If deallocated the route will be removed. 103 | - Parameter queue: A dispatch queue for the callback 104 | - Parameter handler: A ProxyHandler 105 | - Returns: The RouteUUID 106 | */ 107 | 108 | @discardableResult 109 | public func proxy(_ pattern: String, 110 | tags: [String] = [], 111 | owner: RouteOwner? = nil, 112 | queue: DispatchQueue = DispatchQueue.main, 113 | handler: @escaping ProxyHandler) -> RouteUUID { 114 | let proxy = Proxy(pattern, tags: tags, owner: owner ?? self, queue: queue, handler: handler) 115 | accessQueue.async { 116 | self.proxies.insert(proxy, at: 0) 117 | } 118 | 119 | return proxy.uuid 120 | } 121 | 122 | /** 123 | Will execute the first mapped closure and any proxies with matching patterns. Mapped closures 124 | are read in a last to be mapped first executed order. 125 | 126 | - Parameter string: A string represeting a URL 127 | - Parameter passing: Any data that will be passed with a routing 128 | - Returns: A Bool. True if the string is a valid URL and it can open the URL, false otherwise 129 | */ 130 | 131 | @discardableResult 132 | public func open(_ string: String, passing any: Any? = nil) -> Bool { 133 | guard let URL = URL(string: string) else { 134 | return false 135 | } 136 | 137 | return open(URL, passing: any) 138 | } 139 | 140 | /** 141 | Will execute the first mapped closure and any proxies with matching patterns. Mapped closures 142 | are read in a last to be mapped first executed order. 143 | 144 | - Parameter URL: A URL 145 | - Parameter passing: Any data that will be passed with a routing 146 | - Returns: A Bool. True if it can open the URL, false otherwise 147 | */ 148 | 149 | @discardableResult 150 | public func open(_ URL: Foundation.URL, passing any: Any? = nil) -> Bool { 151 | var result = true 152 | 153 | openQueue.sync { 154 | var work = RoutableWork() 155 | guard let searchRoute = searchRoute(from: URL, with: &work.parameters) else { 156 | result = false 157 | return 158 | } 159 | work.searchRoute = searchRoute 160 | 161 | accessQueue.sync { 162 | routes = routes.filter { $0.owner != nil } 163 | proxies = proxies.filter { $0.owner != nil } 164 | work.routes = routes 165 | work.proxies = proxies 166 | for route in work.routes where self.searchRoute(work.searchRoute, matches: route, updating: &work.parameters) { 167 | work.initialRoutable = route 168 | break 169 | } 170 | } 171 | 172 | if work.initialRoutable == nil { 173 | result = false 174 | } 175 | 176 | work.passedAny = any 177 | self.process(work: work) 178 | } 179 | 180 | return result 181 | } 182 | 183 | /** 184 | Removes the route with the given RouteUUID. 185 | 186 | - Parameter of: A RouteUUID 187 | */ 188 | 189 | public func dispose(of uuid: RouteUUID) { 190 | accessQueue.async { 191 | self.routes = self.routes.filter { $0.uuid != uuid } 192 | self.proxies = self.proxies.filter { $0.uuid != uuid } 193 | } 194 | } 195 | 196 | private func process(work: RoutableWork) { 197 | processQueue.async { 198 | let semaphore = DispatchSemaphore(value: 0) 199 | var proxyCommit: ProxyCommit? 200 | for proxy in work.proxies where self.searchRoute(work.searchRoute, matches: proxy) { 201 | proxy.queue.async { 202 | proxy.handler(work.searchRoute, work.parameters, work.passedAny) { commit in 203 | proxyCommit = commit 204 | semaphore.signal() 205 | } 206 | } 207 | semaphore.wait() 208 | if proxyCommit != nil { 209 | break 210 | } 211 | } 212 | 213 | var work = work 214 | var resultingRoutable: Route? 215 | if let commit = proxyCommit { 216 | work.searchRoute = self.searchRoute(from: commit.route, updating: &work.parameters) ?? "" 217 | resultingRoutable = nil 218 | for route in work.routes where self.searchRoute(work.searchRoute, matches: route) { 219 | resultingRoutable = route 220 | break 221 | } 222 | 223 | commit.parameters.forEach { 224 | work.parameters[$0.0] = $0.1 225 | } 226 | work.passedAny = commit.data 227 | } else { 228 | resultingRoutable = work.initialRoutable 229 | } 230 | 231 | if let resultingRoute = resultingRoutable { 232 | resultingRoute.queue.async { 233 | resultingRoute.handler(work.searchRoute, work.parameters, work.passedAny) { 234 | semaphore.signal() 235 | } 236 | } 237 | semaphore.wait() 238 | } 239 | } 240 | } 241 | 242 | private func searchRoute(from URL: URL, with parameters: inout Parameters) -> String? { 243 | guard var components = URLComponents(url: URL, resolvingAgainstBaseURL: false) else { 244 | return nil 245 | } 246 | 247 | components.queryItems?.forEach { 248 | parameters[$0.name] = ($0.value ?? "") 249 | } 250 | components.query = nil 251 | 252 | return components.string 253 | } 254 | 255 | private func searchRoute(from URLString: String, updating parameters: inout Parameters) -> String? { 256 | return URL(string: URLString).flatMap { return searchRoute(from: $0, with: ¶meters) } 257 | } 258 | 259 | private func searchRoute(_ URLString: String, matches routable: Routable) -> Bool { 260 | return _searchRoute(URLString, matches: routable.pattern) != nil 261 | } 262 | 263 | private func searchRoute(_ URLString: String, matches routable: Routable, updating parameters: inout Parameters) -> Bool { 264 | guard let matches = _searchRoute(URLString, matches: routable.pattern) else { 265 | return false 266 | } 267 | 268 | if routable.dynamicSegments.count > 0 && routable.dynamicSegments.count == matches.numberOfRanges - 1 { 269 | for i in (1 ..< matches.numberOfRanges) { 270 | parameters[routable.dynamicSegments[i-1]] = (URLString as NSString) 271 | .substring(with: matches.range(at: i)) 272 | } 273 | } 274 | 275 | return true 276 | } 277 | 278 | private func _searchRoute(_ URLString: String, matches pattern: String) -> NSTextCheckingResult? { 279 | return (try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)) 280 | .flatMap { 281 | $0.matches(in: URLString, options: [], range: NSMakeRange(0, URLString.count)) 282 | }?.first 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /Source/iOS.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import QuartzCore 3 | 4 | public enum ControllerSource { 5 | case storyboard(storyboard: String, identifier: String, bundle: Bundle?) 6 | case nib(controller: UIViewController.Type, name: String?, bundle: Bundle?) 7 | case provided(() -> UIViewController) 8 | } 9 | 10 | public indirect enum PresentationStyle { 11 | case show 12 | case showDetail 13 | case present(animated: Bool) 14 | case push(animated: Bool) 15 | case custom(custom: (_ presenting: UIViewController, 16 | _ presented: UIViewController, 17 | _ completed: Completed) -> Void) 18 | case inNavigationController(PresentationStyle) 19 | } 20 | 21 | public typealias PresentationSetup = (UIViewController, Parameters, Any?) -> Void 22 | 23 | public protocol RoutingPresentationSetup { 24 | func setup(_ route: String, with parameters: Parameters, passing any: Any?) 25 | } 26 | 27 | public extension UINavigationController { 28 | func pushViewController(_ vc: UIViewController, animated: Bool, completion: @escaping Completed) { 29 | self.commit(completion) { 30 | self.pushViewController(vc, animated: animated) 31 | } 32 | } 33 | 34 | @discardableResult 35 | func popViewControllerAnimated(_ animated: Bool, completion: @escaping Completed) -> UIViewController? { 36 | var vc: UIViewController? 37 | self.commit(completion) { 38 | vc = self.popViewController(animated: animated) 39 | } 40 | 41 | return vc 42 | } 43 | 44 | @discardableResult 45 | func popToViewControllerAnimated(_ viewController: UIViewController, animated: Bool, completion: @escaping Completed) -> [UIViewController]? { 46 | var vc: [UIViewController]? 47 | self.commit(completion) { 48 | vc = self.popToViewController(viewController, animated: animated) 49 | } 50 | 51 | return vc 52 | } 53 | 54 | @discardableResult 55 | func popToRootViewControllerAnimated(_ animated: Bool, completion: @escaping Completed) -> [UIViewController]? { 56 | var vc: [UIViewController]? 57 | self.commit(completion) { 58 | vc = self.popToRootViewController(animated: animated) 59 | } 60 | 61 | return vc 62 | } 63 | } 64 | 65 | public extension UIViewController { 66 | func showViewController(_ vc: UIViewController, sender: AnyObject?, completion: @escaping Completed) { 67 | self.commit(completion) { 68 | self.show(vc, sender: sender) 69 | } 70 | } 71 | 72 | func showDetailViewController(_ vc: UIViewController, sender: AnyObject?, completion: @escaping Completed) { 73 | self.commit(completion) { 74 | self.showDetailViewController(vc, sender: sender) 75 | } 76 | } 77 | 78 | fileprivate func commit(_ completed: @escaping Completed, transition: () -> Void) { 79 | CATransaction.begin() 80 | CATransaction.setCompletionBlock(completed) 81 | transition() 82 | CATransaction.commit() 83 | } 84 | } 85 | 86 | @objc internal protocol ControllerIterator { 87 | @objc func nextViewController() -> UIViewController? 88 | } 89 | 90 | extension UITabBarController { 91 | @objc internal override func nextViewController() -> UIViewController? { 92 | return selectedViewController 93 | } 94 | } 95 | 96 | extension UINavigationController { 97 | @objc internal override func nextViewController() -> UIViewController? { 98 | return visibleViewController 99 | } 100 | } 101 | 102 | extension UIViewController : ControllerIterator { 103 | internal func nextViewController() -> UIViewController? { 104 | return presentedViewController 105 | } 106 | } 107 | 108 | public extension Routing { 109 | /** 110 | Associates a view controller presentation to a string pattern. A Routing instance present the 111 | view controller in the event of a matching URL using #open. Routing will only execute the first 112 | matching mapped route. This will be the last route added with #map. 113 | 114 | ```code 115 | let router = Routing() 116 | router.map("routingexample://route", 117 | instance: .Storyboard(storyboard: "Main", identifier: "ViewController", bundle: nil), 118 | style: .Present(animated: true)) { vc, parameters in 119 | ... // Useful callback for setup such as embedding in navigation controller 120 | return vc 121 | } 122 | ``` 123 | 124 | - Parameter pattern: A String pattern 125 | - Parameter tag: A tag to reference when subscripting a Routing object 126 | - Parameter owner: The routes owner. If deallocated the route will be removed. 127 | - Parameter source: The source of the view controller instance 128 | - Parameter style: The presentation style in presenting the view controller 129 | - Parameter setup: A closure provided for additional setup 130 | - Returns: The RouteUUID 131 | */ 132 | 133 | @discardableResult 134 | func map(_ pattern: String, 135 | tags: [String] = ["Views"], 136 | owner: RouteOwner? = nil, 137 | source: ControllerSource, 138 | style: PresentationStyle = .show, 139 | setup: PresentationSetup? = nil) -> RouteUUID { 140 | let routeHandler: RouteHandler = { [unowned self] (route, parameters, any, completed) in 141 | guard let root = UIApplication.shared.keyWindow?.rootViewController else { 142 | completed() 143 | return 144 | } 145 | 146 | let strongSelf = self 147 | let vc = strongSelf.controller(from: source) 148 | (vc as? RoutingPresentationSetup)?.setup(route, with: parameters, passing: any) 149 | setup?(vc, parameters, any) 150 | 151 | var presenter = root 152 | while let nextVC = presenter.nextViewController() { 153 | presenter = nextVC 154 | } 155 | 156 | strongSelf.showController(vc, from: presenter, with: style, completion: completed) 157 | } 158 | 159 | return map(pattern, tags: tags, queue: DispatchQueue.main, owner: owner, handler: routeHandler) 160 | } 161 | 162 | private func controller(from source: ControllerSource) -> UIViewController { 163 | switch source { 164 | case let .storyboard(storyboard, identifier, bundle): 165 | let storyboard = UIStoryboard(name: storyboard, bundle: bundle) 166 | return storyboard.instantiateViewController(withIdentifier: identifier) 167 | case let .nib(controller, name, bundle): 168 | return controller.init(nibName: name, bundle: bundle) 169 | case let .provided(provider): 170 | return provider() 171 | } 172 | } 173 | 174 | private func showController(_ presented: UIViewController, 175 | from presenting: UIViewController, 176 | with style: PresentationStyle, 177 | completion: @escaping Completed) { 178 | switch style { 179 | case .show: 180 | presenting.showViewController(presented, sender: self, completion: completion) 181 | break 182 | case .showDetail: 183 | presenting.showDetailViewController(presented, sender: self, completion: completion) 184 | break 185 | case let .present(animated): 186 | presenting.present(presented, animated: animated, completion: completion) 187 | break 188 | case let .push(animated): 189 | if let presenting = presenting as? UINavigationController { 190 | presenting.pushViewController(presented, animated: animated, completion: completion) 191 | } else { 192 | presenting.navigationController?.pushViewController(presented, animated: animated, completion: completion) 193 | } 194 | case let .custom(custom): 195 | custom(presenting, presented, completion) 196 | break 197 | case let .inNavigationController(style): 198 | showController(UINavigationController(rootViewController: presented), 199 | from: presenting, 200 | with: style, 201 | completion: completion) 202 | break 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/RoutingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Routing 3 | 4 | class RoutingMapTests: XCTestCase { 5 | var router: Routing! 6 | var testingQueue: DispatchQueue! 7 | override func setUp() { 8 | super.setUp() 9 | router = Routing() 10 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent) 11 | } 12 | 13 | func testReturnsTrueIfItCanOpenURL() { 14 | router.map("routingexample://route") { _, _, _, completed in completed() } 15 | 16 | XCTAssertTrue(router.open(URL(string: "routingexample://route/")!)) 17 | } 18 | 19 | func testReturnsTrueIfItCanOpenString() { 20 | router.map("routingexample://route") { _, _, _, completed in completed() } 21 | 22 | XCTAssertTrue(router.open("routingexample://route/")) 23 | } 24 | 25 | func testReturnsFalseIfItCannotOpenURL() { 26 | XCTAssertFalse(router.open(URL(string: "routingexample://incorrectroute/")!)) 27 | } 28 | 29 | func testReturnsFalseIfItCannotOpenString() { 30 | XCTAssertFalse(router.open("routingexample://incorrectroute/")) 31 | } 32 | 33 | func testRouteHandlerIsCalled() { 34 | let expect = expectation(description: "RouteHandler is called.") 35 | router.map("routingexample://route") { _, _, _, completed in 36 | expect.fulfill() 37 | completed() 38 | } 39 | 40 | router.open("routingexample://route") 41 | waitForExpectations(timeout: 0.1, handler: nil) 42 | } 43 | 44 | func testOnlyLatestAddedRouteHandlerIsCalled() { 45 | let expect = expectation(description: "Only latest #mapped RouteHandler is called.") 46 | 47 | var routeCalled = 0 48 | router.map("routingexample://route") { _, _, _, completed in 49 | routeCalled = 1 50 | expect.fulfill() 51 | completed() 52 | } 53 | 54 | router.map("routingexample://route") { _, _, _, completed in 55 | routeCalled = 2 56 | expect.fulfill() 57 | completed() 58 | } 59 | 60 | router.open("routingexample://route") 61 | waitForExpectations(timeout: 0.1, handler: nil) 62 | XCTAssert(routeCalled == 2) 63 | } 64 | 65 | func testMatchingRouteStringPassedToRouteHandler() { 66 | let expect = expectation(description: "Route string is passed to RouteHandler.") 67 | 68 | var matched: String? 69 | router.map("routingexample://route") { route, _, _, completed in 70 | matched = route 71 | expect.fulfill() 72 | completed() 73 | } 74 | 75 | router.open("routingexample://route") 76 | waitForExpectations(timeout: 0.1, handler: nil) 77 | XCTAssert(matched == "routingexample://route") 78 | } 79 | 80 | func testURLArgumentsArePassedToRouteHandler() { 81 | let expect = expectation(description: "URL Arguments are passed to RouteHandler.") 82 | var argument: String? 83 | router.map("routingexample://route/:argument") { _, parameters, _, completed in 84 | argument = parameters["argument"] 85 | expect.fulfill() 86 | completed() 87 | } 88 | 89 | router.open("routingexample://route/expected") 90 | waitForExpectations(timeout: 0.1, handler: nil) 91 | XCTAssert(argument == "expected") 92 | } 93 | 94 | func testQueryParametersArePassedToRouteHandler() { 95 | let expect = expectation(description: "Query param is passed to RouteHandler.") 96 | 97 | var param: String? 98 | router.map("routingexample://route") { _, parameters, _, completed in 99 | param = parameters["param"] 100 | expect.fulfill() 101 | completed() 102 | } 103 | 104 | router.open("routingexample://route?param=expected") 105 | waitForExpectations(timeout: 0.1, handler: nil) 106 | XCTAssert(param == "expected") 107 | } 108 | 109 | func testAnyCanBePassedToRouteHandler() { 110 | let expect = expectation(description: "Any is passed to RouteHandler.") 111 | 112 | var passed: Any? 113 | router.map("routingexample://route") { _, _, any, completed in 114 | passed = any 115 | expect.fulfill() 116 | completed() 117 | } 118 | 119 | router.open("routingexample://route", passing: "any") 120 | waitForExpectations(timeout: 0.1, handler: nil) 121 | if let passed = passed as? String { 122 | XCTAssert(passed == "any") 123 | } else { 124 | XCTFail() 125 | } 126 | } 127 | 128 | func testRouteHandlersAreCalledInSerialOrder() { 129 | let expect = expectation(description: "RouteHandlers are called in serial order.") 130 | 131 | var results = [String]() 132 | router.map("routingexample://route/:append") { _, parameters, _, completed in 133 | results.append(parameters["append"]!) 134 | 135 | self.testingQueue.asyncAfter(deadline: .now() + 1) { 136 | completed() 137 | } 138 | } 139 | 140 | router.map("routingexample://route/two/:append") { _, parameters, _, completed in 141 | results.append(parameters["append"]!) 142 | expect.fulfill() 143 | completed() 144 | } 145 | 146 | router.open(URL(string: "routingexample://route/one")!) 147 | router.open(URL(string: "routingexample://route/two/two")!) 148 | waitForExpectations(timeout: 1.5, handler: nil) 149 | XCTAssert(results == ["one", "two"]) 150 | } 151 | 152 | func testRouterIsAbleToOpenDespiteConcurrentReadWriteAccesses() { 153 | router.map("routingexample://route") { _, _, _, completed in completed() } 154 | 155 | testingQueue.async { 156 | for i in 1...1000 { 157 | self.router.map("\(i)") { _, _, _, completed in completed() } 158 | } 159 | } 160 | 161 | testingQueue.async { 162 | for i in 1...1000 { 163 | self.router.map("\(i)") { _, _, _, completed in completed() } 164 | } 165 | } 166 | 167 | XCTAssertTrue(router.open("routingexample://route")) 168 | } 169 | 170 | func testShouldAllowTheSettingOfARouteHandlerCallbackQueue() { 171 | let expect = expectation(description: "Should allow setting of RouteHandler callback queue.") 172 | 173 | let callbackQueue = DispatchQueue(label: "Testing Call Back Queue", attributes: []) 174 | let key = DispatchSpecificKey() 175 | callbackQueue.setSpecific(key:key, value:()) 176 | router.map("routingexample://route", queue: callbackQueue) { _, _, _, completed in 177 | if let _ = DispatchQueue.getSpecific(key: key) { 178 | expect.fulfill() 179 | } 180 | completed() 181 | } 182 | 183 | router.open("routingexample://route") 184 | waitForExpectations(timeout: 0.1, handler: nil) 185 | } 186 | } 187 | 188 | class RoutingProxyTests: XCTestCase { 189 | var router: Routing! 190 | var testingQueue: DispatchQueue! 191 | override func setUp() { 192 | super.setUp() 193 | router = Routing() 194 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent) 195 | } 196 | 197 | func testCanRedirectOpenedRoute() { 198 | let expect = expectation(description: "Proxy can redirect opened route.") 199 | 200 | var routeCalled = 0 201 | router.map("routingexample://route/one") { _, _, _, completed in 202 | routeCalled = 1 203 | expect.fulfill() 204 | completed() 205 | } 206 | 207 | router.map("routingexample://route/two") { _, _, _, completed in 208 | routeCalled = 2 209 | expect.fulfill() 210 | completed() 211 | } 212 | 213 | router.proxy("routingexample://route/one") { route, parameters, any, next in 214 | next(("routingexample://route/two", parameters, any)) 215 | } 216 | 217 | router.open("routingexample://route/one") 218 | waitForExpectations(timeout: 0.1, handler: nil) 219 | XCTAssert(routeCalled == 2) 220 | } 221 | 222 | func testCanMatchRouteWithWildCard() { 223 | let expect = expectation(description: "Proxy matches route with wildcard.") 224 | 225 | router.map("/route/one") { _, _, _, completed in completed() } 226 | 227 | var isProxied = false 228 | router.proxy("/route/*") { route, parameters, _, next -> Void in 229 | isProxied = true 230 | expect.fulfill() 231 | next(nil) 232 | } 233 | 234 | router.open("routingexample://route/one") 235 | waitForExpectations(timeout: 0.1, handler: nil) 236 | XCTAssertTrue(isProxied) 237 | } 238 | 239 | func testCanModifyURLArgumentsPassedToRouteHandler() { 240 | let expect = expectation(description: "Proxy modifies URL arguments passed to route.") 241 | 242 | var argument: String? 243 | router.map("routingexample://route/:argument") { _, parameters, _, completed in 244 | argument = parameters["argument"] 245 | expect.fulfill() 246 | completed() 247 | } 248 | 249 | router.proxy("routingexample://route/:argument") { route, parameters, any, next in 250 | var parameters = parameters 251 | parameters["argument"] = "two" 252 | next((route, parameters, any)) 253 | } 254 | 255 | router.open("routingexample://route/one") 256 | waitForExpectations(timeout: 0.1, handler: nil) 257 | XCTAssert(argument == "two") 258 | } 259 | 260 | func testCanModifyQueryParametersPassedToRouteHandler() { 261 | let expect = expectation(description: "Proxy modifies query arameters passed to route.") 262 | 263 | var query: String? 264 | router.map("routingexample://route") { _, parameters, _, completed in 265 | query = parameters["query"] 266 | expect.fulfill() 267 | completed() 268 | } 269 | 270 | router.proxy("routingexample://route") { route, parameters, data, next in 271 | var parameters = parameters 272 | parameters["query"] = "bar" 273 | next((route, parameters, data)) 274 | } 275 | 276 | router.open("routingexample://route?query=foo") 277 | waitForExpectations(timeout: 0.1, handler: nil) 278 | XCTAssert(query == "bar") 279 | } 280 | 281 | func testCanModifyAnyPassedToRouteHandler() { 282 | let expect = expectation(description: "Proxy modifies any passed to route.") 283 | 284 | var passed: Any? 285 | router.map("routingexample://route") { _, _, any, completed in 286 | passed = any 287 | expect.fulfill() 288 | completed() 289 | } 290 | 291 | router.proxy("routingexample://route") { route, parameters, any, next in 292 | next((route, parameters, "anotherany")) 293 | } 294 | 295 | router.open("routingexample://route", passing: "any") 296 | waitForExpectations(timeout: 0.1, handler: nil) 297 | if let passed = passed as? String { 298 | XCTAssert(passed == "anotherany") 299 | } else { 300 | XCTFail() 301 | } 302 | } 303 | 304 | func testProxiesAreProcessedUntilAProxyCommitsChangesToNext() { 305 | let expect = expectation(description: "Proxies are called until a commit is made to Next().") 306 | 307 | router.map("routingexample://route") { _, parameters, _, completed in completed() } 308 | 309 | var results = [String]() 310 | router.proxy("routingexample://route") { route, parameters, _, next in 311 | results.append("three") 312 | next(nil) 313 | } 314 | 315 | router.proxy("routingexample://route") { route, parameters, _, next in 316 | results.append("two") 317 | expect.fulfill() 318 | next((route, Parameters(), nil)) 319 | } 320 | 321 | router.proxy("routingexample://route") { route, parameters, _, next in 322 | results.append("one") 323 | next(nil) 324 | } 325 | 326 | router.open(URL(string: "routingexample://route")!) 327 | waitForExpectations(timeout: 0.1, handler: nil) 328 | XCTAssert(results == ["one", "two"]) 329 | } 330 | 331 | func testRouterIsAbleToOpenDespiteConcurrentReadWriteAccesses() { 332 | router.map("routingexample://route") { (_, _, _, completed) in completed() } 333 | 334 | testingQueue.async { 335 | for i in 1...1000 { 336 | self.router.proxy("\(i)") { route, parameters, any, next in next((route, parameters, any)) } 337 | } 338 | } 339 | 340 | testingQueue.async { 341 | for i in 1...1000 { 342 | self.router.proxy("\(i)") { route, parameters, any, next in next((route, parameters, any)) } 343 | } 344 | } 345 | 346 | XCTAssertTrue(router.open("routingexample://route")) 347 | } 348 | 349 | func testParametersAreMaintainedThroughProxyAndRouteHandlers() { 350 | let expect = expectation(description: "Parameters are maintained through proxy and route handlers.") 351 | 352 | var proxiedArgument, proxiedQuery: String? 353 | router.proxy("routingexample://route/:argument") { route, parameters, any, next in 354 | (proxiedArgument, proxiedQuery) = (parameters["argument"], parameters["query"]) 355 | next((route, parameters, any)) 356 | } 357 | 358 | var argument, query: String? 359 | router.map("routingexample://route/:argument") { _, parameters, _, completed in 360 | (argument, query) = (parameters["argument"], parameters["query"]) 361 | expect.fulfill() 362 | completed() 363 | } 364 | 365 | router.open("routingexample://route/foo?query=bar") 366 | waitForExpectations(timeout: 0.1, handler: nil) 367 | XCTAssert(proxiedArgument == "foo") 368 | XCTAssert(argument == "foo") 369 | XCTAssert(proxiedQuery == "bar") 370 | XCTAssert(query == "bar") 371 | } 372 | 373 | func testPassedAnyIsMaintainedThroughProxyAndRouteHandlers() { 374 | let expect = expectation(description: "Passed any is maintained through proxy and route handlers.") 375 | 376 | var proxiedPassed: Any? 377 | router.proxy("routingexample://route") { route, parameters, any, next in 378 | proxiedPassed = any 379 | next((route, parameters, any)) 380 | } 381 | 382 | var passed: Any? 383 | router.map("routingexample://route") { _, _, any, completed in 384 | passed = any 385 | expect.fulfill() 386 | completed() 387 | } 388 | 389 | router.open("routingexample://route", passing: "any") 390 | waitForExpectations(timeout: 0.1, handler: nil) 391 | 392 | if let proxiedPassed = proxiedPassed as? String, let passed = passed as? String { 393 | XCTAssert(proxiedPassed == "any") 394 | XCTAssert(passed == "any") 395 | } else { 396 | XCTFail() 397 | } 398 | } 399 | 400 | func testShouldAllowTheSettingOfAProxyHandlerCallbackQueue() { 401 | let expect = expectation(description: "Should allow setting of ProxyHandler callback queue.") 402 | 403 | let callbackQueue = DispatchQueue(label: "Testing Call Back Queue", attributes: []) 404 | let key = DispatchSpecificKey() 405 | callbackQueue.setSpecific(key:key, value:()) 406 | 407 | router.map("routingexample://route") { (_, _, _, completed) in completed() } 408 | 409 | router.proxy("routingexample://route", queue: callbackQueue) { route, parameters, any, next in 410 | if let _ = DispatchQueue.getSpecific(key: key) { 411 | expect.fulfill() 412 | } 413 | next((route, parameters, any)) 414 | } 415 | 416 | router.open("routingexample://route") 417 | waitForExpectations(timeout: 0.1, handler: nil) 418 | } 419 | } 420 | 421 | class RoutingDisposeTests: XCTestCase { 422 | var router: Routing! 423 | var testingQueue: DispatchQueue! 424 | override func setUp() { 425 | super.setUp() 426 | router = Routing() 427 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent) 428 | } 429 | 430 | func testDisposingOfRouteUUID() { 431 | let uuid = router.map("routingexample://route") { _, _, _, completed in completed() } 432 | XCTAssertTrue(router.open("routingexample://route")) 433 | router.dispose(of: uuid) 434 | XCTAssertFalse(router.open("routingexample://route")) 435 | } 436 | 437 | func testDisposingOfProxyUUID() { 438 | let expect = expectation(description: "Proxy will be disposed and not redirect opened route.") 439 | 440 | var routeCalled = 0 441 | router.map("routingexample://route/one") { _, _, _, completed in 442 | routeCalled = 1 443 | expect.fulfill() 444 | completed() 445 | } 446 | 447 | router.map("routingexample://route/two") { _, _, _, completed in 448 | routeCalled = 2 449 | expect.fulfill() 450 | completed() 451 | } 452 | 453 | let uuid = router.proxy("routingexample://route/one") { route, parameters, any, next in 454 | next(("routingexample://route/two", parameters, any)) 455 | } 456 | router.dispose(of: uuid) 457 | router.open("routingexample://route/one") 458 | waitForExpectations(timeout: 0.1, handler: nil) 459 | XCTAssert(routeCalled == 1) 460 | } 461 | 462 | class testRouteOwner: RouteOwner {} 463 | 464 | func testDeallocatingOfRouterOwnerDisposesMappedRoute() { 465 | var owner: RouteOwner? = testRouteOwner() 466 | router.map("routingexample://route", owner: owner) { _, _, _, completed in completed() } 467 | XCTAssertTrue(router.open("routingexample://route")) 468 | owner = nil 469 | XCTAssertFalse(router.open("routingexample://route")) 470 | } 471 | 472 | func testDeallocatingOfRouterOwnerDisposesProxiedRoute() { 473 | let expect = expectation(description: "Proxy will be disposed and not redirect opened route.") 474 | 475 | var routeCalled = 0 476 | router.map("routingexample://route/one") { _, _, _, completed in 477 | routeCalled = 1 478 | expect.fulfill() 479 | completed() 480 | } 481 | 482 | router.map("routingexample://route/two") { _, _, _, completed in 483 | routeCalled = 2 484 | expect.fulfill() 485 | completed() 486 | } 487 | 488 | var owner: RouteOwner? = testRouteOwner() 489 | router.proxy("routingexample://route/one", owner: owner) { route, parameters, any, next in 490 | next(("routingexample://route/two", parameters, any)) 491 | } 492 | owner = nil 493 | router.open("routingexample://route/one") 494 | waitForExpectations(timeout: 0.1, handler: nil) 495 | XCTAssert(routeCalled == 1) 496 | } 497 | } 498 | 499 | class RoutingTagTests: XCTestCase { 500 | var router: Routing! 501 | var testingQueue: DispatchQueue! 502 | override func setUp() { 503 | super.setUp() 504 | router = Routing() 505 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent) 506 | } 507 | 508 | func testSubscriptingRouterOpen() { 509 | let expect = expectation(description: "When subscripted first #mapped RouteHandler is called.") 510 | 511 | var routeCalled = 0 512 | router.map("routingexample://route", tags: ["First"]) { _, _, _, completed in 513 | routeCalled = 1 514 | expect.fulfill() 515 | completed() 516 | } 517 | 518 | router.map("routingexample://route") { _, _, _, completed in 519 | routeCalled = 2 520 | expect.fulfill() 521 | completed() 522 | } 523 | 524 | router["First"].open("routingexample://route") 525 | waitForExpectations(timeout: 0.1, handler: nil) 526 | XCTAssert(routeCalled == 1) 527 | 528 | } 529 | 530 | func testSubscriptingRouterQueue() { 531 | let expect = expectation(description: "When subscripted order is still preserved.") 532 | 533 | var results = [String]() 534 | router.map("routingexample://route", tags: ["First"]) { _, _, _, completed in 535 | results.append("one") 536 | completed() 537 | } 538 | 539 | router.map("routingexample://route", tags: ["Second"]) { _, _, _, completed in 540 | expect.fulfill() 541 | results.append("two") 542 | completed() 543 | } 544 | 545 | router["First"].open("routingexample://route") 546 | router["Second"].open("routingexample://route") 547 | waitForExpectations(timeout: 0.1, handler: nil) 548 | XCTAssert(results == ["one", "two"]) 549 | } 550 | } 551 | 552 | class RoutingPerformanceTests: XCTestCase { 553 | var router: Routing! 554 | var testingQueue: DispatchQueue! 555 | override func setUp() { 556 | super.setUp() 557 | router = Routing() 558 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent) 559 | } 560 | 561 | func testPerfomance() { 562 | // NOTE: 1000 takes ~8 seconds seems exponential 563 | let num = 100 564 | for i in 1...num { 565 | print("\(i)") 566 | router.map("\(i)") { _, _, _, completed in completed() } 567 | } 568 | 569 | measure { 570 | let expect = self.expectation(description: "Waiting on num + 1.") 571 | let uuid = self.router.map("\(num + 1)") { _, _, _, completed in 572 | expect.fulfill() 573 | completed() 574 | } 575 | 576 | for i in 1...(num + 1) { 577 | self.router.open("\(i)") 578 | } 579 | self.waitForExpectations(timeout: 5, handler: nil) 580 | self.router.dispose(of: uuid) 581 | } 582 | } 583 | } 584 | --------------------------------------------------------------------------------