From 80e604b0e2a0ec253190d12d363a4ff4b324fd0a Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 10:43:13 +0100 Subject: [PATCH 1/8] Add routable --- Compass.xcodeproj/project.pbxproj | 6 ++++++ Sources/iOS/Routable.swift | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 Sources/iOS/Routable.swift diff --git a/Compass.xcodeproj/project.pbxproj b/Compass.xcodeproj/project.pbxproj index 3a73039..8ad3a10 100644 --- a/Compass.xcodeproj/project.pbxproj +++ b/Compass.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ BDF7B4111C3FF43F00576737 /* TestCompass.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF7B40F1C3FF43F00576737 /* TestCompass.swift */; }; BDF7B4151C3FF51100576737 /* Sugar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF7B4141C3FF51100576737 /* Sugar.framework */; }; BDF7B4171C3FF51800576737 /* Sugar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF7B4161C3FF51800576737 /* Sugar.framework */; }; + D5A2A7931C4CEB1C00B51356 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7921C4CEB1C00B51356 /* Routable.swift */; }; + D5A2A7941C4CEB1C00B51356 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7921C4CEB1C00B51356 /* Routable.swift */; }; D5B2E8AA1C3A780C00C0327D /* Compass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5B2E89F1C3A780C00C0327D /* Compass.framework */; }; D5C6294A1C3A7FAA007F7B7C /* Compass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5C629401C3A7FAA007F7B7C /* Compass.framework */; }; /* End PBXBuildFile section */ @@ -43,6 +45,7 @@ BDF7B40F1C3FF43F00576737 /* TestCompass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCompass.swift; sourceTree = ""; }; BDF7B4141C3FF51100576737 /* Sugar.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sugar.framework; path = Carthage/Build/Mac/Sugar.framework; sourceTree = ""; }; BDF7B4161C3FF51800576737 /* Sugar.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sugar.framework; path = Carthage/Build/iOS/Sugar.framework; sourceTree = ""; }; + D5A2A7921C4CEB1C00B51356 /* Routable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routable.swift; sourceTree = ""; }; D5B2E89F1C3A780C00C0327D /* Compass.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Compass.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D5B2E8A91C3A780C00C0327D /* Compass-iOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Compass-iOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D5C629401C3A7FAA007F7B7C /* Compass.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Compass.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -134,6 +137,7 @@ D5C6296A1C3A809D007F7B7C /* iOS */ = { isa = PBXGroup; children = ( + D5A2A7921C4CEB1C00B51356 /* Routable.swift */, BDF7B40D1C3FF41500576737 /* Compass+Navigate.swift */, ); path = iOS; @@ -397,6 +401,7 @@ buildActionMask = 2147483647; files = ( BDF7B4091C3FF40300576737 /* Compass.swift in Sources */, + D5A2A7931C4CEB1C00B51356 /* Routable.swift in Sources */, BDF7B40E1C3FF41500576737 /* Compass+Navigate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -414,6 +419,7 @@ buildActionMask = 2147483647; files = ( BDF7B40A1C3FF40300576737 /* Compass.swift in Sources */, + D5A2A7941C4CEB1C00B51356 /* Routable.swift in Sources */, BDF7B40C1C3FF40A00576737 /* Compass+Navigate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/iOS/Routable.swift b/Sources/iOS/Routable.swift new file mode 100644 index 0000000..c7589ed --- /dev/null +++ b/Sources/iOS/Routable.swift @@ -0,0 +1,6 @@ +import UIKit + +public protocol Routable { + + func resolve(arguments: [String: String], navigationController: UINavigationController) +} From 474f1b94d30ddda1ce776099ae9ed83e49fc3fdf Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 11:34:32 +0100 Subject: [PATCH 2/8] Add router --- Sources/iOS/Router.swift | 14 ++++++++++++++ Sources/iOS/TestRouter.swift | 9 +++++++++ 2 files changed, 23 insertions(+) create mode 100644 Sources/iOS/Router.swift create mode 100644 Sources/iOS/TestRouter.swift diff --git a/Sources/iOS/Router.swift b/Sources/iOS/Router.swift new file mode 100644 index 0000000..d4d46f4 --- /dev/null +++ b/Sources/iOS/Router.swift @@ -0,0 +1,14 @@ +import UIKit + +public struct Router { + + public var routes = [String: Routable]() + + public init() {} + + public func navigate(route: String, arguments: [String: String], navigationController: UINavigationController) { + guard let route = routes[route] else { return } + + route.resolve(arguments, navigationController: navigationController) + } +} diff --git a/Sources/iOS/TestRouter.swift b/Sources/iOS/TestRouter.swift new file mode 100644 index 0000000..b8ea1b7 --- /dev/null +++ b/Sources/iOS/TestRouter.swift @@ -0,0 +1,9 @@ +// +// TestRouter.swift +// Compass +// +// Created by Vadym Markov on 18/01/16. +// Copyright © 2016 Hyper Interaktiv AS. All rights reserved. +// + +import Foundation From 0f012797f837cd66891ae254a72ef743bc418792 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 11:34:39 +0100 Subject: [PATCH 3/8] Router tests --- Compass.xcodeproj/project.pbxproj | 10 +++++++ Sources/iOS/TestRouter.swift | 44 +++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/Compass.xcodeproj/project.pbxproj b/Compass.xcodeproj/project.pbxproj index 8ad3a10..ce4b573 100644 --- a/Compass.xcodeproj/project.pbxproj +++ b/Compass.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ BDF7B4171C3FF51800576737 /* Sugar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF7B4161C3FF51800576737 /* Sugar.framework */; }; D5A2A7931C4CEB1C00B51356 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7921C4CEB1C00B51356 /* Routable.swift */; }; D5A2A7941C4CEB1C00B51356 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7921C4CEB1C00B51356 /* Routable.swift */; }; + D5A2A7971C4CEB7C00B51356 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7961C4CEB7C00B51356 /* Router.swift */; }; + D5A2A7981C4CEB7C00B51356 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7961C4CEB7C00B51356 /* Router.swift */; }; + D5A2A79A1C4CEDDA00B51356 /* TestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7991C4CEDDA00B51356 /* TestRouter.swift */; }; D5B2E8AA1C3A780C00C0327D /* Compass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5B2E89F1C3A780C00C0327D /* Compass.framework */; }; D5C6294A1C3A7FAA007F7B7C /* Compass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5C629401C3A7FAA007F7B7C /* Compass.framework */; }; /* End PBXBuildFile section */ @@ -46,6 +49,8 @@ BDF7B4141C3FF51100576737 /* Sugar.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sugar.framework; path = Carthage/Build/Mac/Sugar.framework; sourceTree = ""; }; BDF7B4161C3FF51800576737 /* Sugar.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sugar.framework; path = Carthage/Build/iOS/Sugar.framework; sourceTree = ""; }; D5A2A7921C4CEB1C00B51356 /* Routable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routable.swift; sourceTree = ""; }; + D5A2A7961C4CEB7C00B51356 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + D5A2A7991C4CEDDA00B51356 /* TestRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestRouter.swift; path = Sources/iOS/TestRouter.swift; sourceTree = SOURCE_ROOT; }; D5B2E89F1C3A780C00C0327D /* Compass.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Compass.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D5B2E8A91C3A780C00C0327D /* Compass-iOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Compass-iOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D5C629401C3A7FAA007F7B7C /* Compass.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Compass.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -139,6 +144,7 @@ children = ( D5A2A7921C4CEB1C00B51356 /* Routable.swift */, BDF7B40D1C3FF41500576737 /* Compass+Navigate.swift */, + D5A2A7961C4CEB7C00B51356 /* Router.swift */, ); path = iOS; sourceTree = ""; @@ -174,6 +180,7 @@ D5C629921C3A8BDA007F7B7C /* iOS */ = { isa = PBXGroup; children = ( + D5A2A7991C4CEDDA00B51356 /* TestRouter.swift */, ); path = iOS; sourceTree = ""; @@ -400,6 +407,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D5A2A7971C4CEB7C00B51356 /* Router.swift in Sources */, BDF7B4091C3FF40300576737 /* Compass.swift in Sources */, D5A2A7931C4CEB1C00B51356 /* Routable.swift in Sources */, BDF7B40E1C3FF41500576737 /* Compass+Navigate.swift in Sources */, @@ -410,6 +418,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D5A2A79A1C4CEDDA00B51356 /* TestRouter.swift in Sources */, BDF7B4101C3FF43F00576737 /* TestCompass.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -418,6 +427,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D5A2A7981C4CEB7C00B51356 /* Router.swift in Sources */, BDF7B40A1C3FF40300576737 /* Compass.swift in Sources */, D5A2A7941C4CEB1C00B51356 /* Routable.swift in Sources */, BDF7B40C1C3FF40A00576737 /* Compass+Navigate.swift in Sources */, diff --git a/Sources/iOS/TestRouter.swift b/Sources/iOS/TestRouter.swift index b8ea1b7..7dcd10e 100644 --- a/Sources/iOS/TestRouter.swift +++ b/Sources/iOS/TestRouter.swift @@ -1,9 +1,37 @@ -// -// TestRouter.swift -// Compass -// -// Created by Vadym Markov on 18/01/16. -// Copyright © 2016 Hyper Interaktiv AS. All rights reserved. -// - import Foundation +import XCTest +import Compass + +class TestRoute: Routable { + + var resolved = false + + func resolve(arguments: [String: String], navigationController: UINavigationController) { + resolved = true + } +} + +class TestRouter: XCTestCase { + + var router: Router! + var navigationController = UINavigationController() + var route: TestRoute! + + override func setUp() { + router = Router() + route = TestRoute() + } + + func testNavigateIfRouteRegistered() { + router.routes["test"] = route + router.navigate("test", arguments: [:], navigationController: navigationController) + + XCTAssertTrue(route.resolved) + } + + func testNavigateIfRouteNotRegistered() { + router.navigate("test", arguments: [:], navigationController: navigationController) + + XCTAssertFalse(route.resolved) + } +} From 9cf1a9cb907fb6756b2d520542e0ad2f58691dd2 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 12:41:03 +0100 Subject: [PATCH 4/8] Optional navigation controller --- Sources/iOS/Routable.swift | 2 +- Sources/iOS/Router.swift | 2 +- Sources/iOS/TestRouter.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/iOS/Routable.swift b/Sources/iOS/Routable.swift index c7589ed..4681f8c 100644 --- a/Sources/iOS/Routable.swift +++ b/Sources/iOS/Routable.swift @@ -2,5 +2,5 @@ import UIKit public protocol Routable { - func resolve(arguments: [String: String], navigationController: UINavigationController) + func resolve(arguments: [String: String], navigationController: UINavigationController?) } diff --git a/Sources/iOS/Router.swift b/Sources/iOS/Router.swift index d4d46f4..e5165b0 100644 --- a/Sources/iOS/Router.swift +++ b/Sources/iOS/Router.swift @@ -6,7 +6,7 @@ public struct Router { public init() {} - public func navigate(route: String, arguments: [String: String], navigationController: UINavigationController) { + public func navigate(route: String, arguments: [String: String], navigationController: UINavigationController?) { guard let route = routes[route] else { return } route.resolve(arguments, navigationController: navigationController) diff --git a/Sources/iOS/TestRouter.swift b/Sources/iOS/TestRouter.swift index 7dcd10e..9d53ec6 100644 --- a/Sources/iOS/TestRouter.swift +++ b/Sources/iOS/TestRouter.swift @@ -6,7 +6,7 @@ class TestRoute: Routable { var resolved = false - func resolve(arguments: [String: String], navigationController: UINavigationController) { + func resolve(arguments: [String: String], navigationController: UINavigationController?) { resolved = true } } From 810a4a6959b4333c6107220b60505b0b050450a5 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 12:41:09 +0100 Subject: [PATCH 5/8] Update readme --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aefa223..c1570dd 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,46 @@ Preferably you would add your own global function that you use for internal navi ## Compass life hacks -##### Tip 1. NavigationHandler.swift +##### Tip 1. Router +We also have some conventional tools for you that could be used to organize your +route handling code and avoid huge `switch` cases. + +- Implement `Routable` protocol to keep your single route navigation code +in one place: +```swift +struct ProfileRoute: Routable { + func resolve(arguments: [String: String], navigationController: UINavigationController) { + guard let username = arguments["username"] else { return } + + let profileController = profileController(title: username) + navigationController?.pushViewController(profileController, animated: true) + } +} +``` + +- Create a `Router` instance and register your routes: +```swift +let router = Router() +router.routes = [ + "profile:{username}" : ProfileRoute(), + // "login:{username}" : LoginRoute() +] +``` + +- Parse URL with **Compass** and navigate to the route with a help of your +`Router` instance. +```swift +func application(app: UIApplication, + openURL url: NSURL, + options: [String : AnyObject]) -> Bool { + return Compass.parse(url) { route, arguments in + router.navigate(route, arguments: arguments, + navigationController: navigationController) + } +} +``` + +##### Tip 2. Navigation handler You could have multiple handlers depending on if a user is logged in or not. ```swift struct NavigationHandler { @@ -102,7 +141,24 @@ struct NavigationHandler { } ``` -##### Tip 2. Compass.swift +If you use `Router`-based approach you could set up 2 routers depending on the +auth state. +```swift +let routerPreLogin = Router() +routerPreLogin.routes = [ + "profile:{username}" : ProfileRoute() +] + +let routerPostLogin = Router() +routerPostLogin.routes = [ + "login:{username}" : LoginRoute() +] + +let router = isLoggedIn ? routerPostLogin : routerPreLogin +router.navigate(route, arguments: arguments, navigationController: navigationController) +``` + +##### Tip 3. Global function Add your own global function to easily navigate internally ``` swift import Compass From d3594c1cef9d91e05450f591bae2ed08613250f5 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 12:44:11 +0100 Subject: [PATCH 6/8] Add logout route to example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1570dd..03106c3 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ struct ProfileRoute: Routable { let router = Router() router.routes = [ "profile:{username}" : ProfileRoute(), - // "login:{username}" : LoginRoute() + // "logout" : LogoutRoute() ] ``` From 36d224777c095f63535cd4466640596f1a74a220 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 12:48:01 +0100 Subject: [PATCH 7/8] Remove new files from Mac target --- Compass.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Compass.xcodeproj/project.pbxproj b/Compass.xcodeproj/project.pbxproj index ce4b573..fcc4d3a 100644 --- a/Compass.xcodeproj/project.pbxproj +++ b/Compass.xcodeproj/project.pbxproj @@ -16,9 +16,7 @@ BDF7B4151C3FF51100576737 /* Sugar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF7B4141C3FF51100576737 /* Sugar.framework */; }; BDF7B4171C3FF51800576737 /* Sugar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF7B4161C3FF51800576737 /* Sugar.framework */; }; D5A2A7931C4CEB1C00B51356 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7921C4CEB1C00B51356 /* Routable.swift */; }; - D5A2A7941C4CEB1C00B51356 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7921C4CEB1C00B51356 /* Routable.swift */; }; D5A2A7971C4CEB7C00B51356 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7961C4CEB7C00B51356 /* Router.swift */; }; - D5A2A7981C4CEB7C00B51356 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7961C4CEB7C00B51356 /* Router.swift */; }; D5A2A79A1C4CEDDA00B51356 /* TestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2A7991C4CEDDA00B51356 /* TestRouter.swift */; }; D5B2E8AA1C3A780C00C0327D /* Compass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5B2E89F1C3A780C00C0327D /* Compass.framework */; }; D5C6294A1C3A7FAA007F7B7C /* Compass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5C629401C3A7FAA007F7B7C /* Compass.framework */; }; @@ -427,9 +425,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D5A2A7981C4CEB7C00B51356 /* Router.swift in Sources */, BDF7B40A1C3FF40300576737 /* Compass.swift in Sources */, - D5A2A7941C4CEB1C00B51356 /* Routable.swift in Sources */, BDF7B40C1C3FF40A00576737 /* Compass+Navigate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; From 66eb2895c41ec0730267aba33133a4474088c14b Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Mon, 18 Jan 2016 12:49:50 +0100 Subject: [PATCH 8/8] Add optional --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03106c3..c213778 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ route handling code and avoid huge `switch` cases. in one place: ```swift struct ProfileRoute: Routable { - func resolve(arguments: [String: String], navigationController: UINavigationController) { + func resolve(arguments: [String: String], navigationController: UINavigationController?) { guard let username = arguments["username"] else { return } let profileController = profileController(title: username)