From 5f3f770bad4f70a587f3cbe3634e58610fb101f1 Mon Sep 17 00:00:00 2001 From: Eliah Snakin Date: Wed, 23 Aug 2017 14:49:15 +0300 Subject: [PATCH] Fast & dirty route arguments implementation --- Demo/AppCoordinator.swift | 8 ++- Demo/Presenters/AboutPresenter.swift | 2 +- Demo/Presenters/LoginPresenter.swift | 2 +- Demo/Presenters/NavigationPresenter.swift | 4 +- Demo/Presenters/RegistrationPresenter.swift | 2 +- Demo/Presenters/Step2Presenter.swift | 10 +++- Demo/Presenters/TabBarPresenter.swift | 4 +- Demo/Presenters/WelcomePresenter.swift | 2 +- Demo/String+extensions.swift | 42 ++++++++++++++ Demo/ViewControllers/MockViewController.swift | 7 ++- Demo/ViewModels/MockViewModel.swift | 5 +- FeatherweightRouter.xcodeproj/project.pbxproj | 47 +++------------- Sources/CachedPresenter.swift | 28 ---------- Sources/Presenter.swift | 41 +++++++------- Sources/Router+junction.swift | 25 +++++---- Sources/Router+route.swift | 51 +++++++++-------- Sources/Router+stack.swift | 18 +++--- Sources/Router.swift | 56 +++++++++++-------- 18 files changed, 183 insertions(+), 171 deletions(-) create mode 100644 Demo/String+extensions.swift delete mode 100644 Sources/CachedPresenter.swift diff --git a/Demo/AppCoordinator.swift b/Demo/AppCoordinator.swift index 8ad551c..7b7bbb4 100644 --- a/Demo/AppCoordinator.swift +++ b/Demo/AppCoordinator.swift @@ -1,7 +1,7 @@ import UIKit import FeatherweightRouter -typealias UIPresenter = Presenter +typealias UIPresenter = RoutePresenter func appCoordinator() -> UIViewController { @@ -11,7 +11,7 @@ func appCoordinator() -> UIViewController { store.setPath("welcome") - return router.presentable + return router.getPresentable() } func createRouter(_ store: AppStore) -> Router { @@ -21,7 +21,9 @@ func createRouter(_ store: AppStore) -> Router { Router(navigationPresenter("Welcome")).stack([ Router(welcomePresenter(store)).route(predicate: {$0 == "welcome"}, children: [ Router(registrationPresenter(store)).route(predicate: {$0 == "welcome/register"}, children: [ - Router(step2Presenter(store)).route(predicate: {$0 == "welcome/register/step2"}), + Router(step2Presenter(store)).route(predicate: {$0.matches("welcome/register/step(?\\d+)")}, arguments: { (path) -> RouteArguments in + return path.capturedGroups(withRegex: "welcome/register/step(?\\d+)") + }), ]), Router(loginPresenter(store)).route(predicate: {$0 == "welcome/login"}), ]) diff --git a/Demo/Presenters/AboutPresenter.swift b/Demo/Presenters/AboutPresenter.swift index 83cac1b..9d69107 100644 --- a/Demo/Presenters/AboutPresenter.swift +++ b/Demo/Presenters/AboutPresenter.swift @@ -10,5 +10,5 @@ func aboutPresenter(_ store: AppStore) -> UIPresenter { callToActionTitle: "Go to '/welcome/login/'", callToActionRoute: "welcome/login")) - return Presenter(getPresentable: { viewController }) + return RoutePresenter(getPresentable: { _ in viewController }) } diff --git a/Demo/Presenters/LoginPresenter.swift b/Demo/Presenters/LoginPresenter.swift index f53bb53..5d3f18c 100644 --- a/Demo/Presenters/LoginPresenter.swift +++ b/Demo/Presenters/LoginPresenter.swift @@ -10,5 +10,5 @@ func loginPresenter(_ store: AppStore) -> UIPresenter { callToActionTitle: "Go to '/welcome/'", callToActionRoute: "welcome")) - return Presenter(getPresentable: { viewController }) + return RoutePresenter(getPresentable: { _ in viewController }) } diff --git a/Demo/Presenters/NavigationPresenter.swift b/Demo/Presenters/NavigationPresenter.swift index a92f394..c5bf1b7 100644 --- a/Demo/Presenters/NavigationPresenter.swift +++ b/Demo/Presenters/NavigationPresenter.swift @@ -7,8 +7,8 @@ func navigationPresenter(_ title: String) -> UIPresenter { navigationController.tabBarItem.title = title navigationController.tabBarItem.image = UIImage(named: "placeholder-icon") - return Presenter( - getPresentable: { navigationController }, + return RoutePresenter( + getPresentable: { _ in navigationController }, setChild: { _ in }, setChildren: { navigationController.setViewControllers($0, animated: true) }) } diff --git a/Demo/Presenters/RegistrationPresenter.swift b/Demo/Presenters/RegistrationPresenter.swift index 37e8d2d..eaee759 100644 --- a/Demo/Presenters/RegistrationPresenter.swift +++ b/Demo/Presenters/RegistrationPresenter.swift @@ -10,5 +10,5 @@ func registrationPresenter(_ store: AppStore) -> UIPresenter { callToActionTitle: "Go to '/about/'", callToActionRoute: "about")) - return Presenter(getPresentable: { viewController }) + return RoutePresenter(getPresentable: { _ in viewController }) } diff --git a/Demo/Presenters/Step2Presenter.swift b/Demo/Presenters/Step2Presenter.swift index 0503eb0..63357e2 100644 --- a/Demo/Presenters/Step2Presenter.swift +++ b/Demo/Presenters/Step2Presenter.swift @@ -6,9 +6,15 @@ func step2Presenter(_ store: AppStore) -> UIPresenter { let viewController = MockViewController(MockViewModel( store: store, backgroundColor: (128, 255, 255), - title: "Registration + Step 2", + title: "ROUTE ARGUMENT: Step = ", callToActionTitle: "Go to '/welcome/register/'", callToActionRoute: "welcome/register")) - return Presenter(getPresentable: { viewController }) + return RoutePresenter(getPresentable: { + arguments in + if let step = arguments.first { + viewController.setStepNumber(step) + } + return viewController + }) } diff --git a/Demo/Presenters/TabBarPresenter.swift b/Demo/Presenters/TabBarPresenter.swift index a77b412..9781f9a 100644 --- a/Demo/Presenters/TabBarPresenter.swift +++ b/Demo/Presenters/TabBarPresenter.swift @@ -5,8 +5,8 @@ func tabBarPresenter() -> UIPresenter { let tabBarController = UITabBarController() - return Presenter( - getPresentable: { tabBarController }, + return RoutePresenter( + getPresentable: { _ in tabBarController }, setChild: { tabBarController.selectedViewController = $0 }, setChildren: { tabBarController.setViewControllers($0, animated: true) }) } diff --git a/Demo/Presenters/WelcomePresenter.swift b/Demo/Presenters/WelcomePresenter.swift index 542ace5..1e3fbe8 100644 --- a/Demo/Presenters/WelcomePresenter.swift +++ b/Demo/Presenters/WelcomePresenter.swift @@ -5,5 +5,5 @@ func welcomePresenter(_ store: AppStore) -> UIPresenter { let viewController = WelcomeViewController(WelcomeViewModel(store: store)) - return Presenter(getPresentable: { viewController }) + return RoutePresenter(getPresentable: { _ in viewController }) } diff --git a/Demo/String+extensions.swift b/Demo/String+extensions.swift new file mode 100644 index 0000000..64982db --- /dev/null +++ b/Demo/String+extensions.swift @@ -0,0 +1,42 @@ +// +// String+extensions.swift +// FeatherweightRouter +// +// Created by Eliah Snakin on 23/08/2017. +// Copyright © 2017 Featherweight Labs. All rights reserved. +// + +import Foundation + +extension String { + + func matches(_ regex: String) -> Bool { + return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil + } + + func capturedGroups(withRegex pattern: String) -> [String] { + var results = [String]() + + var regex: NSRegularExpression + do { + regex = try NSRegularExpression(pattern: pattern, options: []) + } catch { + return results + } + + let matches = regex.matches(in: self, options: [], range: NSRange(location:0, length: self.characters.count)) + + guard let match = matches.first else { return results } + + let lastRangeIndex = match.numberOfRanges - 1 + guard lastRangeIndex >= 1 else { return results } + + for i in 1...lastRangeIndex { + let capturedGroupIndex = match.rangeAt(i) + let matchedString = (self as NSString).substring(with: capturedGroupIndex) + results.append(matchedString) + } + + return results + } +} diff --git a/Demo/ViewControllers/MockViewController.swift b/Demo/ViewControllers/MockViewController.swift index b9ffa4b..9689fbc 100644 --- a/Demo/ViewControllers/MockViewController.swift +++ b/Demo/ViewControllers/MockViewController.swift @@ -10,6 +10,7 @@ protocol ProvidesMockData { class MockViewController: UIViewController { let viewModel: ProvidesMockData + var step: String? @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var actionButton: UIButton! @@ -25,6 +26,10 @@ class MockViewController: UIViewController { tabBarItem.title = viewModel.title tabBarItem.image = UIImage(named: "placeholder-icon") } + + public func setStepNumber(_ step: String) { + self.step = step + } override func viewDidLoad() { super.viewDidLoad() @@ -34,7 +39,7 @@ class MockViewController: UIViewController { blue: CGFloat(viewModel.backgroundColor.2) / 255, alpha: 1) - titleLabel.text = viewModel.title + titleLabel.text = viewModel.title + (step ?? "") actionButton.setTitle(viewModel.callToActionTitle, for: UIControlState()) diff --git a/Demo/ViewModels/MockViewModel.swift b/Demo/ViewModels/MockViewModel.swift index 75ea4bb..cba6bb7 100644 --- a/Demo/ViewModels/MockViewModel.swift +++ b/Demo/ViewModels/MockViewModel.swift @@ -6,9 +6,10 @@ struct MockViewModel: ProvidesMockData { let title: String let callToActionTitle: String let navigateToAction: () -> Void + var arbitraryArgument: String? init(store: AppStore, backgroundColor: RGBColor, title: String, callToActionTitle: String, - callToActionRoute: String) { + callToActionRoute: String, arbitraryArgument: String? = nil) { self.backgroundColor = backgroundColor self.title = title @@ -17,5 +18,7 @@ struct MockViewModel: ProvidesMockData { navigateToAction = { store.dispatchRoute(callToActionRoute) } + + self.arbitraryArgument = arbitraryArgument } } diff --git a/FeatherweightRouter.xcodeproj/project.pbxproj b/FeatherweightRouter.xcodeproj/project.pbxproj index b8a718b..4e6f749 100644 --- a/FeatherweightRouter.xcodeproj/project.pbxproj +++ b/FeatherweightRouter.xcodeproj/project.pbxproj @@ -35,8 +35,6 @@ 737DC1351D0E68A000704F6C /* RouterRouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DC1331D0E68A000704F6C /* RouterRouteTests.swift */; }; 737DC1371D0E918F00704F6C /* RouterStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DC1361D0E918F00704F6C /* RouterStackTests.swift */; }; 737DC1381D0E918F00704F6C /* RouterStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DC1361D0E918F00704F6C /* RouterStackTests.swift */; }; - 737DC13A1D0EA46800704F6C /* CachedPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DC1391D0EA46800704F6C /* CachedPresenter.swift */; }; - 737DC13B1D0EA46800704F6C /* CachedPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DC1391D0EA46800704F6C /* CachedPresenter.swift */; }; 737DC13D1D0EA69300704F6C /* CachedPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DC13C1D0EA69300704F6C /* CachedPresenterTests.swift */; }; 737DC13E1D0EA69300704F6C /* CachedPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DC13C1D0EA69300704F6C /* CachedPresenterTests.swift */; }; 737DF5A11D0D63A900614CC0 /* PresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737DF5A01D0D63A900614CC0 /* PresenterTests.swift */; }; @@ -59,6 +57,8 @@ 7386DBBD1C5F5C3E00D385A9 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7386DBBC1C5F5C3E00D385A9 /* UIKit+Extensions.swift */; }; 73ED16781C3E2E44003B3827 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73ED16771C3E2E44003B3827 /* WelcomeViewModel.swift */; }; 73ED167A1C3E2E65003B3827 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73ED16791C3E2E65003B3827 /* WelcomeViewController.swift */; }; + D04D96AA1F4DA18100F03703 /* String+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE898E1F4D9D1200CD69FC /* String+extensions.swift */; }; + D0CE898F1F4D9D1200CD69FC /* String+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE898E1F4D9D1200CD69FC /* String+extensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -110,7 +110,6 @@ 73612BC31C3E13B100FD841A /* RegistrationPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationPresenter.swift; sourceTree = ""; }; 737DC1331D0E68A000704F6C /* RouterRouteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterRouteTests.swift; sourceTree = ""; }; 737DC1361D0E918F00704F6C /* RouterStackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterStackTests.swift; sourceTree = ""; }; - 737DC1391D0EA46800704F6C /* CachedPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPresenter.swift; sourceTree = ""; }; 737DC13C1D0EA69300704F6C /* CachedPresenterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPresenterTests.swift; sourceTree = ""; }; 737DC1401D0EAAE400704F6C /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 737DC1411D0EAAE400704F6C /* .hound.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .hound.yml; sourceTree = ""; }; @@ -131,6 +130,7 @@ 7386DBBC1C5F5C3E00D385A9 /* UIKit+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+Extensions.swift"; sourceTree = ""; }; 73ED16771C3E2E44003B3827 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = ""; }; 73ED16791C3E2E65003B3827 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; + D0CE898E1F4D9D1200CD69FC /* String+extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "String+extensions.swift"; path = "Demo/String+extensions.swift"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -203,7 +203,6 @@ 731836C51C5D867B00D5E1C6 /* FeatherweightRouter.h */, 731836C61C5D867B00D5E1C6 /* Info.plist */, 731836F81C5D87ED00D5E1C6 /* Array+pickFirst.swift */, - 737DC1391D0EA46800704F6C /* CachedPresenter.swift */, 731836FE1C5D87ED00D5E1C6 /* Presenter.swift */, 731836FD1C5D87ED00D5E1C6 /* Router.swift */, 731836FA1C5D87ED00D5E1C6 /* Router+junction.swift */, @@ -251,6 +250,7 @@ 7349E7AC1C3DDA47004A507B /* Assets.xcassets */, 7349E7AD1C3DDA47004A507B /* LaunchScreen.storyboard */, 7349E7AF1C3DDA47004A507B /* Info.plist */, + D0CE898E1F4D9D1200CD69FC /* String+extensions.swift */, ); path = "Supporting Files"; sourceTree = ""; @@ -341,7 +341,6 @@ 7349E73B1C3DD665004A507B /* Frameworks */, 7349E73C1C3DD665004A507B /* Headers */, 7349E73D1C3DD665004A507B /* Resources */, - 731837091C5D887C00D5E1C6 /* SwiftLint */, ); buildRules = ( ); @@ -395,7 +394,6 @@ 737DF5A31D0D6EB200614CC0 /* Frameworks */, 737DF5A41D0D6EB200614CC0 /* Headers */, 737DF5A51D0D6EB200614CC0 /* Resources */, - 737DC1491D0EABA800704F6C /* SwiftLint */, ); buildRules = ( ); @@ -523,48 +521,17 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 731837091C5D887C00D5E1C6 /* SwiftLint */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = SwiftLint; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"error: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; - }; - 737DC1491D0EABA800704F6C /* SwiftLint */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = SwiftLint; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"error: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 7349E73A1C3DD665004A507B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 731837011C5D87ED00D5E1C6 /* Array+pickFirst.swift in Sources */, + D0CE898F1F4D9D1200CD69FC /* String+extensions.swift in Sources */, 731837071C5D87ED00D5E1C6 /* Presenter.swift in Sources */, 731837061C5D87ED00D5E1C6 /* Router.swift in Sources */, 731837081C5D87ED00D5E1C6 /* Router+stack.swift in Sources */, 731837051C5D87ED00D5E1C6 /* Router+route.swift in Sources */, - 737DC13A1D0EA46800704F6C /* CachedPresenter.swift in Sources */, 731837031C5D87ED00D5E1C6 /* Router+junction.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -592,6 +559,7 @@ 7349E7B71C3DDAD2004A507B /* AppCoordinator.swift in Sources */, 731777831C68D8CB00806D29 /* TabBarPresenter.swift in Sources */, 7317777E1C68D44F00806D29 /* MockViewModel.swift in Sources */, + D04D96AA1F4DA18100F03703 /* String+extensions.swift in Sources */, 73ED16781C3E2E44003B3827 /* WelcomeViewModel.swift in Sources */, 73612BC51C3E13BD00FD841A /* RegistrationPresenter.swift in Sources */, 7317777C1C68D44600806D29 /* AboutPresenter.swift in Sources */, @@ -615,7 +583,6 @@ 737DF5C41D0D6FC400614CC0 /* Router.swift in Sources */, 737DF5C51D0D6FC400614CC0 /* Router+junction.swift in Sources */, 737DF5BE1D0D6F2200614CC0 /* Array+pickFirst.swift in Sources */, - 737DC13B1D0EA46800704F6C /* CachedPresenter.swift in Sources */, 737DF5C71D0D6FC400614CC0 /* Router+stack.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -665,6 +632,7 @@ 7349E7451C3DD665004A507B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -735,6 +703,7 @@ 7349E7461C3DD665004A507B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; diff --git a/Sources/CachedPresenter.swift b/Sources/CachedPresenter.swift deleted file mode 100644 index fac4939..0000000 --- a/Sources/CachedPresenter.swift +++ /dev/null @@ -1,28 +0,0 @@ -/** - Creates a lazy wrapper around a presenter creation function that wraps presenter scope, but does - not get created until invoked. - - `(() -> Presentable) -> Presenter` - - - parameter createPresentable: callable that returns the presentable item - - - returns: Presenter - */ -public func cachedPresenter( - _ createPresentable: @escaping () -> ViewController) - -> Presenter { - - weak var presentable: ViewController? = nil - - func maybeCachedPresentable() -> ViewController { - if let cachedPresentable = presentable { - return cachedPresentable - } - - let newPresentable = createPresentable() - presentable = newPresentable - return newPresentable - } - - return Presenter(getPresentable: maybeCachedPresentable) -} diff --git a/Sources/Presenter.swift b/Sources/Presenter.swift index 33f400d..71dcf64 100644 --- a/Sources/Presenter.swift +++ b/Sources/Presenter.swift @@ -1,58 +1,57 @@ /** - + To control the display or change of route, each router needs a presenter to hand off to. The presenter separates the route tree from interface code. - + The actual presenter is owned by the presenter which can control the lifecycle of ViewControllers. - + It is a structure that bridges commands between the Router and View. As such about the only thing we need to test is that the commands are forwarded correctly. - + */ -public struct Presenter { - +public struct RoutePresenter { + /// Returns the child presenter - public var getPresentable: () -> ViewController - + public var getPresentable: (RouteArguments) -> ViewController + /// Sets the child value to the passed in presenter public var setChild: (ViewController) -> Void = { _ in fatalError("Call to unset setChild") } - + /// Sets the children value to passed in presenters public var setChildren: ([ViewController]) -> Void = { _ in fatalError("Call to unset setChildren") } - - /// The owned presenter - public var presentable: ViewController { return getPresentable() } - + /** Shorthand for the setChild function - + - parameter child: child presenter */ public func set(_ child: ViewController) { setChild(child) } - + /** Shorthand for the setChildren function - + - parameter children: presenter children */ public func set(_ children: [ViewController]) { setChildren(children) } - + /** Presenter initialiser - + - parameter getPresentable: Returns the owned presenter - parameter setChild: Callback action to set the child presenter - parameter setChildren: Callback to set the children presenters + - parameter setRouteOptions: Callback to parametrize presenter with route options */ - public init(getPresentable: @escaping (Void) -> ViewController, + public init(getPresentable: @escaping (RouteArguments) -> ViewController, setChild: ((ViewController) -> Void)? = nil, - setChildren: (([ViewController]) -> Void)? = nil) { + setChildren: (([ViewController]) -> Void)? = nil) + { self.getPresentable = getPresentable if let setChild = setChild { self.setChild = setChild @@ -62,3 +61,5 @@ public struct Presenter { } } } + +public typealias RouteArguments = [String] diff --git a/Sources/Router+junction.swift b/Sources/Router+junction.swift index 185bac6..b6d3402 100644 --- a/Sources/Router+junction.swift +++ b/Sources/Router+junction.swift @@ -1,41 +1,42 @@ extension Router { - + /** Junction Router - + A fork in the road. You can see all the paths in front of you (setChildren), but can only drive down one path at a time (setChild). - + The junction function is a Router behaviour customiser. It replaces handlesRoute and setRoute with functions that behave like a Junction Router and returns a modified copy of the Router. - + - parameter junctions: [Router], an array of child routers to present - + - returns: Router, a customised copy of self */ public func junction(_ junctions: [Router]) -> Router { - + var router = self - + router.handlesRoute = { path in return junctions.contains { $0.handlesRoute(path) } } - + router.setRoute = { path in + // inform the junction presenter of the available children - router.presenter.set(junctions.map { $0.presentable }) - + router.presenter.set(junctions.map { $0.getPresentable() }) + if let junction = junctions.pickFirst({ $0.handlesRoute(path) ? $0 : nil }) { // if a child matches, pass the path to it _ = junction.setRoute(path) // and set it as the active junction - router.presenter.set(junction.presentable) + router.presenter.set(junction.getPresentable()) return true } return false } - + return router } } diff --git a/Sources/Router+route.swift b/Sources/Router+route.swift index 78946f4..f5598fc 100644 --- a/Sources/Router+route.swift +++ b/Sources/Router+route.swift @@ -1,39 +1,42 @@ extension Router { - + /** Route extension - + A named destination. Unlike the Junction and Stack, Route is a valid endpoint. To drive to town, there may be multiple routes with varying junctions and paths to take, but the destination will remain the same. - + - parameter predicate: A String containing a regex that possibly matches to provided paths. - parameter children: Array of little Router children. - + - returns: A customised copy of Router */ - public func route(predicate - pathMatches: @escaping ((Path) -> Bool), children: [Router] = []) -> - Router { - - var router = self - - router.handlesRoute = { path in - return pathMatches(path) || children.contains { $0.handlesRoute(path) } + public func route(predicate pathMatches: @escaping ((Path) -> Bool), + children: [Router] = [], + arguments: ((Path) -> RouteArguments)? = nil + ) -> Router + { + + var router = self + + router.handlesRoute = { path in + return pathMatches(path) || children.contains { $0.handlesRoute(path) } + } + + router.getStack = { path in + let routeOptions = arguments != nil ? arguments!(path) : nil + if pathMatches(path) { + return [router.getPresentable(options: routeOptions)] } - - router.getStack = { path in - if pathMatches(path) { - return [router.presenter.presentable] + for child in children { + if let stack = child.getStack(path) { + return [router.getPresentable(options: routeOptions)] + stack } - for child in children { - if let stack = child.getStack(path) { - return [router.presenter.presentable] + stack - } - } - return nil } - - return router + return nil + } + + return router } } diff --git a/Sources/Router+stack.swift b/Sources/Router+stack.swift index 5f8b758..ccf280b 100644 --- a/Sources/Router+stack.swift +++ b/Sources/Router+stack.swift @@ -1,31 +1,31 @@ extension Router { - + /** Stack Router - + Photos of every town you drive through to get to the destination. You may not be able to see the alternative paths, but you can navigate back to the start by traveling back through the waypoints in the stack. - + The Stack router behaviour is analogous to NavigationControllers. - + - parameter stack: Array of routes that may also have child routes. - + - returns: A customised copy of Router */ public func stack(_ stack: [Router]) -> Router { - + var router = self - + router.handlesRoute = { path in return stack.contains { $0.handlesRoute(path) } } - + router.setRoute = { path in router.presenter.set(stack.pickFirst { $0.getStack(path) } ?? []) return true } - + return router } } diff --git a/Sources/Router.swift b/Sources/Router.swift index 78129d7..546e4a6 100644 --- a/Sources/Router.swift +++ b/Sources/Router.swift @@ -1,42 +1,45 @@ /** - + The Router is a structure that collects functions together that are related to the same routing unit. - + Each Router also requires a Presenter, through which any required changes are passed to. - + The generic type - most likely a ViewController - is the item to be presented if this route is evaluated and determined to be a match to the current route. - + The current route path is set via a String. Paths are expected to be normalised before being passed to the Router, in so much as any string passed in - if valid - should be in a state that is able to match the appropriate route selector. - + Ie, hostname and protocol stripped, and string down-cased if required. - + */ public struct Router { - + /** Presenter to pass route actions too */ - public var presenter: Presenter - + public var presenter: RoutePresenter + /** The presenter to return if this route matches */ - public var presentable: ViewController { return presenter.presentable } - + // public var presentable: ViewController { return presenter.presentable } + public func getPresentable(options: RouteArguments? = nil) -> ViewController { + return presenter.getPresentable(options ?? []) + } + /** Determines if this Router handles the passed in String */ public var handlesRoute: (Path) -> Bool = { _ in false } - + /** Passes actions to the Presenter to update the view to the provided String */ public var setRoute: (Path) -> Bool = { _ in false } - + /** Returns an array presenters (T) that match the passed in String. The actual array returned can vary depending in the behaviour assigned to this router. Ie, a Junction Router returns an @@ -44,33 +47,38 @@ public struct Router { nested children to find a match and returns the matched ancestor tree. */ public var getStack: (Path) -> [ViewController]? = { _ in nil } - + + /** + Passes route options to presenter + */ + public var setRouteOptions: (Path) -> RouteArguments? = { _ in nil } + /** Primary Router initialiser - + Sets each of router variables to the passed in closures. - + - parameter presenter: Presenter - parameter handlesRoute: (route: String) -> Bool - parameter setRoute: (route: String) -> Void - parameter getStack: (route: String) -> [T]? */ public init( - _ presenter: Presenter, - handlesRoute: ((Path) -> Bool)? = nil, - setRoute: ((Path) -> Bool)? = nil, - getStack: ((Path) -> [ViewController]?)? = nil) { - + _ presenter: RoutePresenter, + handlesRoute: ((Path) -> Bool)? = nil, + setRoute: ((Path) -> Bool)? = nil, + getStack: ((Path) -> [ViewController]?)? = nil) { + self.presenter = presenter - + if let handlesRoute = handlesRoute { self.handlesRoute = handlesRoute } - + if let setRoute = setRoute { self.setRoute = setRoute } - + if let getStack = getStack { self.getStack = getStack }