├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cordux.podspec
├── Cordux.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── Cordux-iOS.xcscheme
│ └── Cordux-tvOS.xcscheme
├── Cordux
├── Cordux.h
└── Info.plist
├── CorduxTests
├── Info.plist
├── NavigationControllerMetaCoordinatorTests.swift
├── SimpleCoordinators.swift
├── SimpleState.swift
└── SubscriptionsTest.swift
├── Example
├── .ruby-version
├── CREDITS.md
├── CorduxPrototype.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
├── CorduxPrototype.xcworkspace
│ └── contents.xcworkspacedata
├── CorduxPrototype
│ ├── AppCoordinator.swift
│ ├── AppDelegate.swift
│ ├── AppState.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── first.imageset
│ │ │ ├── Contents.json
│ │ │ └── first.pdf
│ │ └── second.imageset
│ │ │ ├── Contents.json
│ │ │ └── second.pdf
│ ├── Authentication.storyboard
│ ├── AuthenticationCoordinator.swift
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Catalog.storyboard
│ ├── CatalogCoordinator.swift
│ ├── FirstViewController.swift
│ ├── ForgotPasswordViewController.swift
│ ├── Info.plist
│ ├── Main.storyboard
│ ├── MainViewController.swift
│ ├── SecondViewController.swift
│ └── SignInViewController.swift
├── CorduxPrototypeTests
│ ├── CorduxPrototypeTests.swift
│ └── Info.plist
├── CorduxPrototypeUITests
│ ├── CorduxPrototypeUITests.swift
│ └── Info.plist
├── Podfile
├── Podfile.lock
└── Pods
│ ├── Local Podspecs
│ └── Cordux.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ └── project.pbxproj
│ └── Target Support Files
│ ├── Cordux
│ ├── Cordux-dummy.m
│ ├── Cordux-prefix.pch
│ ├── Cordux-umbrella.h
│ ├── Cordux.modulemap
│ ├── Cordux.xcconfig
│ └── Info.plist
│ └── Pods-CorduxPrototype
│ ├── Info.plist
│ ├── Pods-CorduxPrototype-acknowledgements.markdown
│ ├── Pods-CorduxPrototype-acknowledgements.plist
│ ├── Pods-CorduxPrototype-dummy.m
│ ├── Pods-CorduxPrototype-frameworks.sh
│ ├── Pods-CorduxPrototype-resources.sh
│ ├── Pods-CorduxPrototype-umbrella.h
│ ├── Pods-CorduxPrototype.debug.xcconfig
│ ├── Pods-CorduxPrototype.modulemap
│ └── Pods-CorduxPrototype.release.xcconfig
├── LICENSE
├── README.md
├── Sources
├── Context.swift
├── Coordinator.swift
├── Logging.swift
├── Middleware.swift
├── NavigationControllerCoordinator.swift
├── NavigationControllerMetaCoordinator.swift
├── PresentingCoordinator.swift
├── Reducer.swift
├── Routing.swift
├── SceneCoordinator.swift
├── Scenes.swift
├── SimpleCoordinator.swift
├── Store.swift
├── Subscriptions.swift
├── TabBarControllerCoordinator.swift
├── TypeHelpers.swift
└── ViewControllerLifecycle.swift
├── diagrams
├── Makefile
└── architecture.dot
└── yougot.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | .DS_Store
6 |
7 | ## Build generated
8 | build/
9 | DerivedData/
10 |
11 | ## Various settings
12 | *.pbxuser
13 | !default.pbxuser
14 | *.mode1v3
15 | !default.mode1v3
16 | *.mode2v3
17 | !default.mode2v3
18 | *.perspectivev3
19 | !default.perspectivev3
20 | xcuserdata/
21 |
22 | ## Other
23 | *.moved-aside
24 | *.xccheckout
25 | *.xcscmblueprint
26 |
27 | # Graphviz Output
28 | diagrams/*.png
29 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at open.source.conduct@willowtreeapps.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Cordux
2 |
3 | Looking to contribute something to Cordux? Here's how you can help.
4 |
5 | Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
6 |
7 | Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features.
8 |
9 | ## Code of Conduct
10 |
11 | WillowTree, Inc. has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://github.com/willowtreeapps/cordux/blob/develop/CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated.
12 |
13 | ## Our Development Process
14 |
15 | ### Test Suite
16 |
17 | We run the unit test target in the latest Xcode 8.
18 |
19 | ### Pull Requests
20 |
21 | **Working on your first Pull Request?** You can learn how from this _free_ series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github).
22 |
23 | _Before_ submitting a pull request, please make sure the following is done:
24 |
25 | 1. Fork the repo and create your branch from `develop`.
26 | 2. If you've added code that should be tested, add tests!
27 | 3. If you've changed APIs, update the documentation.
28 | 4. Ensure the test suite passes.
29 |
30 | ## Bugs
31 |
32 | ### Where to Find Known Issues
33 |
34 | We will be using GitHub Issues for our public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn't already exist.
35 |
36 | ### Reporting New Issues
37 |
38 | The best way to get your bug fixed is to provide a reduced test case.
39 |
40 | ## Style Guide
41 |
42 | <<< OPTIONAL - Include any code conventions, documentation conventions, linter specifics, etc >>>
43 |
44 | ## License
45 |
46 | By submitting a pull request, you represent that you have the right to license your contribution to WillowTree and the community, and agree by submitting the patch that your contributions are licensed under the [MIT License](https://github.com/willowtreeapps/cordux/blob/develop/LICENSE).
47 |
--------------------------------------------------------------------------------
/Cordux.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "Cordux"
3 | s.version = "0.1.7"
4 | s.summary = "App Coordinators & Redux. A framework for UIKit development."
5 | s.description = <<-DESC
6 | Cordux combines app coordiantors with a redux-like architecture.
7 |
8 | This allows rapid and powerful UI development with full support
9 | for routing and deep linking.
10 | DESC
11 |
12 | s.homepage = "http://github.com/willowtreeapps/cordux"
13 | s.license = "MIT"
14 | s.authors = {
15 | "Ian Terrell" => "ian.terrell@gmail.com",
16 | }
17 |
18 | s.source = {
19 | :git => "https://github.com/willowtreeapps/cordux.git",
20 | :tag => "v0.1.7"
21 | }
22 |
23 | s.platform = :ios, :tvos
24 | s.ios.deployment_target = "9.0"
25 | s.tvos.deployment_target = "9.1"
26 |
27 | s.source_files = "Sources/*.swift"
28 | end
29 |
--------------------------------------------------------------------------------
/Cordux.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Cordux.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Cordux.xcodeproj/xcshareddata/xcschemes/Cordux-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/Cordux.xcodeproj/xcshareddata/xcschemes/Cordux-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/Cordux/Cordux.h:
--------------------------------------------------------------------------------
1 | //
2 | // Cordux.h
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 7/28/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Cordux.
12 | FOUNDATION_EXPORT double CorduxVersionNumber;
13 |
14 | //! Project version string for Cordux.
15 | FOUNDATION_EXPORT const unsigned char CorduxVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Cordux/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.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/CorduxTests/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 |
--------------------------------------------------------------------------------
/CorduxTests/NavigationControllerMetaCoordinatorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationControllerMetaCoordinatorTests.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 12/23/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Cordux
11 |
12 | class NavigationControllerMetaCoordinatorTests: XCTestCase {
13 | static let store = Store(initialState: SimpleAppState(), reducer: SimpleAppReducer())
14 |
15 | final class TestCoordinator: NavigationControllerMetaCoordinator {
16 | var store = NavigationControllerMetaCoordinatorTests.store
17 | var navigationController = UINavigationController()
18 | var coordinators: [AnyCoordinator] = []
19 | func start(route: Route?) {}
20 | func coordinators(for route: Route) -> [AnyCoordinator] {
21 | return []
22 | }
23 |
24 | func setCoordinators(for routes: [RouteConvertible]) {
25 | coordinators = routes.map(BasicCoordinator.init)
26 | }
27 | }
28 |
29 | var coordinator: TestCoordinator!
30 |
31 | override func setUp() {
32 | coordinator = TestCoordinator()
33 | coordinator.setCoordinators(for: ["hello", "world", "who", "there?"])
34 | }
35 |
36 | func testNumberWithAllSharedCoordinators() {
37 | let number = coordinator.numberOfLastExistingCoordinator(for: Route(components: ["hello", "world", "who", "there?"]))
38 | XCTAssertEqual(4, number)
39 | }
40 |
41 | func testNumberWithNoSharedCoordinators() {
42 | let number = coordinator.numberOfLastExistingCoordinator(for: Route(components: ["x", "hello", "world", "who", "there?"]))
43 | XCTAssertEqual(0, number)
44 | }
45 |
46 | func testNumberWithNoSharedCoordinatorsSubset() {
47 | let number = coordinator.numberOfLastExistingCoordinator(for: Route(components: ["world", "who", "there?"]))
48 | XCTAssertEqual(0, number)
49 | }
50 |
51 | func testNumberWith1SharedCoordinators() {
52 | let number = coordinator.numberOfLastExistingCoordinator(for: Route(components: ["hello", "x", "world", "who", "there?"]))
53 | XCTAssertEqual(1, number)
54 | }
55 |
56 | func testNumberWith2SharedCoordinators() {
57 | let number = coordinator.numberOfLastExistingCoordinator(for: Route(components: ["hello", "world", "x", "who", "there?"]))
58 | XCTAssertEqual(2, number)
59 | }
60 |
61 | func testNumberWith3SharedCoordinators() {
62 | let number = coordinator.numberOfLastExistingCoordinator(for: Route(components: ["hello", "world", "who", "x", "there?"]))
63 | XCTAssertEqual(3, number)
64 | }
65 |
66 | func testNumberWithSomeSharedCoordinators() {
67 | let number = coordinator.numberOfLastExistingCoordinator(for: Route(components: ["hello", "world", "dogs", "cats"]))
68 | XCTAssertEqual(2, number)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/CorduxTests/SimpleCoordinators.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleCoordinators.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 12/23/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cordux
11 |
12 | final class BasicCoordinator: SimpleCoordinator {
13 | let store = Store(initialState: SimpleAppState(), reducer: SimpleAppReducer())
14 | let rootViewController = UIViewController()
15 | let route: Route
16 |
17 | init(route: RouteConvertible) {
18 | self.route = route.route()
19 | }
20 |
21 | func start(route: Route?) {}
22 | }
23 |
--------------------------------------------------------------------------------
/CorduxTests/SimpleState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleState.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 8/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cordux
11 |
12 | struct SimpleAppState: StateType {
13 | var route: Route = []
14 | var auth: SimpleAuthState = .signedOut
15 | }
16 |
17 | enum SimpleAuthState {
18 | case signedOut
19 | case signedIn(name: String)
20 | }
21 |
22 | class SimpleAppReducer: Reducer {
23 | func handleAction(_ action: Action, state: SimpleAppState) -> SimpleAppState {
24 | guard let action = action as? SimpleAction else {
25 | return state
26 | }
27 |
28 | var state = state
29 |
30 | switch action {
31 | case .signIn(let name):
32 | state.auth = .signedIn(name: name)
33 | default:
34 | break
35 | }
36 |
37 | return state
38 | }
39 | }
40 |
41 | struct SimpleAuthViewModel {
42 | let name: String
43 | }
44 |
45 | extension SimpleAuthViewModel {
46 | init?(state: SimpleAppState) {
47 | guard case let .signedIn(name) = state.auth else {
48 | return nil
49 | }
50 |
51 | self.name = name
52 | }
53 | }
54 |
55 | enum SimpleAction: Action {
56 | case noop
57 | case signIn(name: String)
58 | }
59 |
--------------------------------------------------------------------------------
/CorduxTests/SubscriptionsTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubscriptionsTest.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 8/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Cordux
11 |
12 | class SubscriptionTestsSubscriber: SubscriberType {
13 | var lastModel: SimpleAuthViewModel?
14 | func newState(_ viewModel: SimpleAuthViewModel?) {
15 | lastModel = viewModel
16 | }
17 | }
18 |
19 | class SubscriptionsTest: XCTestCase {
20 |
21 | var store: Store!
22 |
23 | override func setUp() {
24 | super.setUp()
25 |
26 | store = Store(initialState: SimpleAppState(), reducer: SimpleAppReducer())
27 | }
28 |
29 | func testNilOptionalSubscription() {
30 | let sub = SubscriptionTestsSubscriber()
31 | store.subscribe(sub, SimpleAuthViewModel.init)
32 | store.dispatch(SimpleAction.noop)
33 | XCTAssertNil(sub.lastModel)
34 | }
35 |
36 | func testNonNilOptionalSubscription() {
37 | let sub = SubscriptionTestsSubscriber()
38 | store.subscribe(sub, SimpleAuthViewModel.init)
39 | store.dispatch(SimpleAction.signIn(name: "bob"))
40 | XCTAssertNotNil(sub.lastModel)
41 | XCTAssertEqual("bob", sub.lastModel?.name)
42 | }
43 |
44 | func testUnsubscription() {
45 | let sub = SubscriptionTestsSubscriber()
46 | XCTAssert(store.isNewSubscriber(sub))
47 |
48 | store.subscribe(sub, SimpleAuthViewModel.init)
49 | XCTAssert(!store.isNewSubscriber(sub))
50 |
51 | store.unsubscribe(sub)
52 | XCTAssert(store.isNewSubscriber(sub))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Example/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.3.1
2 |
--------------------------------------------------------------------------------
/Example/CREDITS.md:
--------------------------------------------------------------------------------
1 | The store, reducer, and subscription code originated in [ReSwift](https://github.com/ReSwift/ReSwift).
2 |
3 | ```
4 | The MIT License (MIT) Copyright (c) 2016 ReSwift Contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in the
8 | Software without restriction, including without limitation the rights to use, copy,
9 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
10 | and to permit persons to whom the Software is furnished to do so, subject to the
11 | following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ```
23 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 11F987EDEA335C26DE6118EC /* Pods_CorduxPrototype.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D68095426899A4DDA2A6DE17 /* Pods_CorduxPrototype.framework */; };
11 | D922A4F51D465A6B002FF3E7 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D922A4F41D465A6B002FF3E7 /* MainViewController.swift */; };
12 | D922A4F91D46980F002FF3E7 /* Cordux.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D922A4F81D46980F002FF3E7 /* Cordux.framework */; };
13 | D946E9421D41027200DB4FCF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9411D41027200DB4FCF /* AppDelegate.swift */; };
14 | D946E9441D41027200DB4FCF /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9431D41027200DB4FCF /* FirstViewController.swift */; };
15 | D946E9461D41027200DB4FCF /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9451D41027200DB4FCF /* SecondViewController.swift */; };
16 | D946E94B1D41027200DB4FCF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D946E94A1D41027200DB4FCF /* Assets.xcassets */; };
17 | D946E94E1D41027200DB4FCF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D946E94C1D41027200DB4FCF /* LaunchScreen.storyboard */; };
18 | D946E9591D41027200DB4FCF /* CorduxPrototypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9581D41027200DB4FCF /* CorduxPrototypeTests.swift */; };
19 | D946E9641D41027200DB4FCF /* CorduxPrototypeUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9631D41027200DB4FCF /* CorduxPrototypeUITests.swift */; };
20 | D946E9721D41039E00DB4FCF /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9711D41039E00DB4FCF /* AppCoordinator.swift */; };
21 | D946E9741D4103CE00DB4FCF /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9731D4103CE00DB4FCF /* AppState.swift */; };
22 | D946E9761D4105DF00DB4FCF /* Catalog.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D946E9751D4105DF00DB4FCF /* Catalog.storyboard */; };
23 | D946E9781D41064D00DB4FCF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D946E9771D41064D00DB4FCF /* Main.storyboard */; };
24 | D946E97D1D41078A00DB4FCF /* CatalogCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E97C1D41078A00DB4FCF /* CatalogCoordinator.swift */; };
25 | D946E9801D410A2400DB4FCF /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D946E97F1D410A2400DB4FCF /* Authentication.storyboard */; };
26 | D946E9821D410AE600DB4FCF /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9811D410AE600DB4FCF /* AuthenticationCoordinator.swift */; };
27 | D946E9841D410B0500DB4FCF /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9831D410B0500DB4FCF /* SignInViewController.swift */; };
28 | D946E9861D410B1500DB4FCF /* ForgotPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D946E9851D410B1500DB4FCF /* ForgotPasswordViewController.swift */; };
29 | /* End PBXBuildFile section */
30 |
31 | /* Begin PBXContainerItemProxy section */
32 | D946E9551D41027200DB4FCF /* PBXContainerItemProxy */ = {
33 | isa = PBXContainerItemProxy;
34 | containerPortal = D946E9361D41027200DB4FCF /* Project object */;
35 | proxyType = 1;
36 | remoteGlobalIDString = D946E93D1D41027200DB4FCF;
37 | remoteInfo = CorduxPrototype;
38 | };
39 | D946E9601D41027200DB4FCF /* PBXContainerItemProxy */ = {
40 | isa = PBXContainerItemProxy;
41 | containerPortal = D946E9361D41027200DB4FCF /* Project object */;
42 | proxyType = 1;
43 | remoteGlobalIDString = D946E93D1D41027200DB4FCF;
44 | remoteInfo = CorduxPrototype;
45 | };
46 | /* End PBXContainerItemProxy section */
47 |
48 | /* Begin PBXFileReference section */
49 | 54AB738FE12A7D14DACD3E78 /* Pods-CorduxPrototype.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CorduxPrototype.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype.debug.xcconfig"; sourceTree = ""; };
50 | B2AF9DBFC9F31A01AD177704 /* Pods-CorduxPrototype.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CorduxPrototype.release.xcconfig"; path = "Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype.release.xcconfig"; sourceTree = ""; };
51 | D68095426899A4DDA2A6DE17 /* Pods_CorduxPrototype.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CorduxPrototype.framework; sourceTree = BUILT_PRODUCTS_DIR; };
52 | D922A4F41D465A6B002FF3E7 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
53 | D922A4F61D4697C1002FF3E7 /* Pods_CorduxPrototype.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods_CorduxPrototype.framework; path = "../../../Library/Developer/Xcode/DerivedData/CorduxPrototype-fwijtkdjhhjcmubaqbjyuijepfer/Build/Products/Debug-iphonesimulator/Pods_CorduxPrototype.framework"; sourceTree = ""; };
54 | D922A4F81D46980F002FF3E7 /* Cordux.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cordux.framework; path = "Pods/../build/Debug-iphoneos/Cordux/Cordux.framework"; sourceTree = ""; };
55 | D946E93E1D41027200DB4FCF /* CorduxPrototype.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CorduxPrototype.app; sourceTree = BUILT_PRODUCTS_DIR; };
56 | D946E9411D41027200DB4FCF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
57 | D946E9431D41027200DB4FCF /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; };
58 | D946E9451D41027200DB4FCF /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; };
59 | D946E94A1D41027200DB4FCF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
60 | D946E94D1D41027200DB4FCF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
61 | D946E94F1D41027200DB4FCF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
62 | D946E9541D41027200DB4FCF /* CorduxPrototypeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CorduxPrototypeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63 | D946E9581D41027200DB4FCF /* CorduxPrototypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorduxPrototypeTests.swift; sourceTree = ""; };
64 | D946E95A1D41027200DB4FCF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
65 | D946E95F1D41027200DB4FCF /* CorduxPrototypeUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CorduxPrototypeUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
66 | D946E9631D41027200DB4FCF /* CorduxPrototypeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorduxPrototypeUITests.swift; sourceTree = ""; };
67 | D946E9651D41027200DB4FCF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
68 | D946E9711D41039E00DB4FCF /* AppCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; };
69 | D946E9731D4103CE00DB4FCF /* AppState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; };
70 | D946E9751D4105DF00DB4FCF /* Catalog.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Catalog.storyboard; sourceTree = ""; };
71 | D946E9771D41064D00DB4FCF /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; };
72 | D946E97C1D41078A00DB4FCF /* CatalogCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CatalogCoordinator.swift; sourceTree = ""; };
73 | D946E97F1D410A2400DB4FCF /* Authentication.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = ""; };
74 | D946E9811D410AE600DB4FCF /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = ""; };
75 | D946E9831D410B0500DB4FCF /* SignInViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignInViewController.swift; sourceTree = ""; };
76 | D946E9851D410B1500DB4FCF /* ForgotPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForgotPasswordViewController.swift; sourceTree = ""; };
77 | /* End PBXFileReference section */
78 |
79 | /* Begin PBXFrameworksBuildPhase section */
80 | D946E93B1D41027200DB4FCF /* Frameworks */ = {
81 | isa = PBXFrameworksBuildPhase;
82 | buildActionMask = 2147483647;
83 | files = (
84 | D922A4F91D46980F002FF3E7 /* Cordux.framework in Frameworks */,
85 | 11F987EDEA335C26DE6118EC /* Pods_CorduxPrototype.framework in Frameworks */,
86 | );
87 | runOnlyForDeploymentPostprocessing = 0;
88 | };
89 | D946E9511D41027200DB4FCF /* Frameworks */ = {
90 | isa = PBXFrameworksBuildPhase;
91 | buildActionMask = 2147483647;
92 | files = (
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | D946E95C1D41027200DB4FCF /* Frameworks */ = {
97 | isa = PBXFrameworksBuildPhase;
98 | buildActionMask = 2147483647;
99 | files = (
100 | );
101 | runOnlyForDeploymentPostprocessing = 0;
102 | };
103 | /* End PBXFrameworksBuildPhase section */
104 |
105 | /* Begin PBXGroup section */
106 | 19007DAF54767E72B45238B6 /* Frameworks */ = {
107 | isa = PBXGroup;
108 | children = (
109 | D922A4F81D46980F002FF3E7 /* Cordux.framework */,
110 | D922A4F61D4697C1002FF3E7 /* Pods_CorduxPrototype.framework */,
111 | D68095426899A4DDA2A6DE17 /* Pods_CorduxPrototype.framework */,
112 | );
113 | name = Frameworks;
114 | sourceTree = "";
115 | };
116 | 4D2C81F7E511D38610FEE565 /* Pods */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 54AB738FE12A7D14DACD3E78 /* Pods-CorduxPrototype.debug.xcconfig */,
120 | B2AF9DBFC9F31A01AD177704 /* Pods-CorduxPrototype.release.xcconfig */,
121 | );
122 | name = Pods;
123 | sourceTree = "";
124 | };
125 | D946E9351D41027200DB4FCF = {
126 | isa = PBXGroup;
127 | children = (
128 | D946E9401D41027200DB4FCF /* CorduxPrototype */,
129 | D946E9571D41027200DB4FCF /* CorduxPrototypeTests */,
130 | D946E9621D41027200DB4FCF /* CorduxPrototypeUITests */,
131 | D946E93F1D41027200DB4FCF /* Products */,
132 | 19007DAF54767E72B45238B6 /* Frameworks */,
133 | 4D2C81F7E511D38610FEE565 /* Pods */,
134 | );
135 | sourceTree = "";
136 | };
137 | D946E93F1D41027200DB4FCF /* Products */ = {
138 | isa = PBXGroup;
139 | children = (
140 | D946E93E1D41027200DB4FCF /* CorduxPrototype.app */,
141 | D946E9541D41027200DB4FCF /* CorduxPrototypeTests.xctest */,
142 | D946E95F1D41027200DB4FCF /* CorduxPrototypeUITests.xctest */,
143 | );
144 | name = Products;
145 | sourceTree = "";
146 | };
147 | D946E9401D41027200DB4FCF /* CorduxPrototype */ = {
148 | isa = PBXGroup;
149 | children = (
150 | D946E9411D41027200DB4FCF /* AppDelegate.swift */,
151 | D946E9731D4103CE00DB4FCF /* AppState.swift */,
152 | D946E9791D41065C00DB4FCF /* Scenes */,
153 | D946E94A1D41027200DB4FCF /* Assets.xcassets */,
154 | D946E94C1D41027200DB4FCF /* LaunchScreen.storyboard */,
155 | D946E94F1D41027200DB4FCF /* Info.plist */,
156 | );
157 | path = CorduxPrototype;
158 | sourceTree = "";
159 | };
160 | D946E9571D41027200DB4FCF /* CorduxPrototypeTests */ = {
161 | isa = PBXGroup;
162 | children = (
163 | D946E9581D41027200DB4FCF /* CorduxPrototypeTests.swift */,
164 | D946E95A1D41027200DB4FCF /* Info.plist */,
165 | );
166 | path = CorduxPrototypeTests;
167 | sourceTree = "";
168 | };
169 | D946E9621D41027200DB4FCF /* CorduxPrototypeUITests */ = {
170 | isa = PBXGroup;
171 | children = (
172 | D946E9631D41027200DB4FCF /* CorduxPrototypeUITests.swift */,
173 | D946E9651D41027200DB4FCF /* Info.plist */,
174 | );
175 | path = CorduxPrototypeUITests;
176 | sourceTree = "";
177 | };
178 | D946E9791D41065C00DB4FCF /* Scenes */ = {
179 | isa = PBXGroup;
180 | children = (
181 | D946E97A1D41066900DB4FCF /* App */,
182 | D946E97E1D410A1700DB4FCF /* Authentication */,
183 | D946E97B1D41074A00DB4FCF /* Catalog */,
184 | );
185 | name = Scenes;
186 | sourceTree = "";
187 | };
188 | D946E97A1D41066900DB4FCF /* App */ = {
189 | isa = PBXGroup;
190 | children = (
191 | D946E9771D41064D00DB4FCF /* Main.storyboard */,
192 | D946E9711D41039E00DB4FCF /* AppCoordinator.swift */,
193 | D922A4F41D465A6B002FF3E7 /* MainViewController.swift */,
194 | );
195 | name = App;
196 | sourceTree = "";
197 | };
198 | D946E97B1D41074A00DB4FCF /* Catalog */ = {
199 | isa = PBXGroup;
200 | children = (
201 | D946E97C1D41078A00DB4FCF /* CatalogCoordinator.swift */,
202 | D946E9751D4105DF00DB4FCF /* Catalog.storyboard */,
203 | D946E9431D41027200DB4FCF /* FirstViewController.swift */,
204 | D946E9451D41027200DB4FCF /* SecondViewController.swift */,
205 | );
206 | name = Catalog;
207 | sourceTree = "";
208 | };
209 | D946E97E1D410A1700DB4FCF /* Authentication */ = {
210 | isa = PBXGroup;
211 | children = (
212 | D946E9811D410AE600DB4FCF /* AuthenticationCoordinator.swift */,
213 | D946E97F1D410A2400DB4FCF /* Authentication.storyboard */,
214 | D946E9831D410B0500DB4FCF /* SignInViewController.swift */,
215 | D946E9851D410B1500DB4FCF /* ForgotPasswordViewController.swift */,
216 | );
217 | name = Authentication;
218 | sourceTree = "";
219 | };
220 | /* End PBXGroup section */
221 |
222 | /* Begin PBXNativeTarget section */
223 | D946E93D1D41027200DB4FCF /* CorduxPrototype */ = {
224 | isa = PBXNativeTarget;
225 | buildConfigurationList = D946E9681D41027200DB4FCF /* Build configuration list for PBXNativeTarget "CorduxPrototype" */;
226 | buildPhases = (
227 | 895DFEE12740A16FE021B9F6 /* [CP] Check Pods Manifest.lock */,
228 | D946E93A1D41027200DB4FCF /* Sources */,
229 | D946E93B1D41027200DB4FCF /* Frameworks */,
230 | D946E93C1D41027200DB4FCF /* Resources */,
231 | B1CB19B0F0318728D98E0C4D /* [CP] Embed Pods Frameworks */,
232 | C6907585B2FAEA8147E749B2 /* [CP] Copy Pods Resources */,
233 | );
234 | buildRules = (
235 | );
236 | dependencies = (
237 | );
238 | name = CorduxPrototype;
239 | productName = CorduxPrototype;
240 | productReference = D946E93E1D41027200DB4FCF /* CorduxPrototype.app */;
241 | productType = "com.apple.product-type.application";
242 | };
243 | D946E9531D41027200DB4FCF /* CorduxPrototypeTests */ = {
244 | isa = PBXNativeTarget;
245 | buildConfigurationList = D946E96B1D41027200DB4FCF /* Build configuration list for PBXNativeTarget "CorduxPrototypeTests" */;
246 | buildPhases = (
247 | D946E9501D41027200DB4FCF /* Sources */,
248 | D946E9511D41027200DB4FCF /* Frameworks */,
249 | D946E9521D41027200DB4FCF /* Resources */,
250 | );
251 | buildRules = (
252 | );
253 | dependencies = (
254 | D946E9561D41027200DB4FCF /* PBXTargetDependency */,
255 | );
256 | name = CorduxPrototypeTests;
257 | productName = CorduxPrototypeTests;
258 | productReference = D946E9541D41027200DB4FCF /* CorduxPrototypeTests.xctest */;
259 | productType = "com.apple.product-type.bundle.unit-test";
260 | };
261 | D946E95E1D41027200DB4FCF /* CorduxPrototypeUITests */ = {
262 | isa = PBXNativeTarget;
263 | buildConfigurationList = D946E96E1D41027200DB4FCF /* Build configuration list for PBXNativeTarget "CorduxPrototypeUITests" */;
264 | buildPhases = (
265 | D946E95B1D41027200DB4FCF /* Sources */,
266 | D946E95C1D41027200DB4FCF /* Frameworks */,
267 | D946E95D1D41027200DB4FCF /* Resources */,
268 | );
269 | buildRules = (
270 | );
271 | dependencies = (
272 | D946E9611D41027200DB4FCF /* PBXTargetDependency */,
273 | );
274 | name = CorduxPrototypeUITests;
275 | productName = CorduxPrototypeUITests;
276 | productReference = D946E95F1D41027200DB4FCF /* CorduxPrototypeUITests.xctest */;
277 | productType = "com.apple.product-type.bundle.ui-testing";
278 | };
279 | /* End PBXNativeTarget section */
280 |
281 | /* Begin PBXProject section */
282 | D946E9361D41027200DB4FCF /* Project object */ = {
283 | isa = PBXProject;
284 | attributes = {
285 | LastSwiftUpdateCheck = 0730;
286 | LastUpgradeCheck = 0810;
287 | ORGANIZATIONNAME = WillowTree;
288 | TargetAttributes = {
289 | D946E93D1D41027200DB4FCF = {
290 | CreatedOnToolsVersion = 7.3.1;
291 | LastSwiftMigration = 0800;
292 | };
293 | D946E9531D41027200DB4FCF = {
294 | CreatedOnToolsVersion = 7.3.1;
295 | LastSwiftMigration = 0800;
296 | TestTargetID = D946E93D1D41027200DB4FCF;
297 | };
298 | D946E95E1D41027200DB4FCF = {
299 | CreatedOnToolsVersion = 7.3.1;
300 | LastSwiftMigration = 0800;
301 | TestTargetID = D946E93D1D41027200DB4FCF;
302 | };
303 | };
304 | };
305 | buildConfigurationList = D946E9391D41027200DB4FCF /* Build configuration list for PBXProject "CorduxPrototype" */;
306 | compatibilityVersion = "Xcode 3.2";
307 | developmentRegion = English;
308 | hasScannedForEncodings = 0;
309 | knownRegions = (
310 | en,
311 | Base,
312 | );
313 | mainGroup = D946E9351D41027200DB4FCF;
314 | productRefGroup = D946E93F1D41027200DB4FCF /* Products */;
315 | projectDirPath = "";
316 | projectRoot = "";
317 | targets = (
318 | D946E93D1D41027200DB4FCF /* CorduxPrototype */,
319 | D946E9531D41027200DB4FCF /* CorduxPrototypeTests */,
320 | D946E95E1D41027200DB4FCF /* CorduxPrototypeUITests */,
321 | );
322 | };
323 | /* End PBXProject section */
324 |
325 | /* Begin PBXResourcesBuildPhase section */
326 | D946E93C1D41027200DB4FCF /* Resources */ = {
327 | isa = PBXResourcesBuildPhase;
328 | buildActionMask = 2147483647;
329 | files = (
330 | D946E94E1D41027200DB4FCF /* LaunchScreen.storyboard in Resources */,
331 | D946E94B1D41027200DB4FCF /* Assets.xcassets in Resources */,
332 | D946E9761D4105DF00DB4FCF /* Catalog.storyboard in Resources */,
333 | D946E9781D41064D00DB4FCF /* Main.storyboard in Resources */,
334 | D946E9801D410A2400DB4FCF /* Authentication.storyboard in Resources */,
335 | );
336 | runOnlyForDeploymentPostprocessing = 0;
337 | };
338 | D946E9521D41027200DB4FCF /* Resources */ = {
339 | isa = PBXResourcesBuildPhase;
340 | buildActionMask = 2147483647;
341 | files = (
342 | );
343 | runOnlyForDeploymentPostprocessing = 0;
344 | };
345 | D946E95D1D41027200DB4FCF /* Resources */ = {
346 | isa = PBXResourcesBuildPhase;
347 | buildActionMask = 2147483647;
348 | files = (
349 | );
350 | runOnlyForDeploymentPostprocessing = 0;
351 | };
352 | /* End PBXResourcesBuildPhase section */
353 |
354 | /* Begin PBXShellScriptBuildPhase section */
355 | 895DFEE12740A16FE021B9F6 /* [CP] Check Pods Manifest.lock */ = {
356 | isa = PBXShellScriptBuildPhase;
357 | buildActionMask = 2147483647;
358 | files = (
359 | );
360 | inputPaths = (
361 | );
362 | name = "[CP] Check Pods Manifest.lock";
363 | outputPaths = (
364 | );
365 | runOnlyForDeploymentPostprocessing = 0;
366 | shellPath = /bin/sh;
367 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
368 | showEnvVarsInLog = 0;
369 | };
370 | B1CB19B0F0318728D98E0C4D /* [CP] Embed Pods Frameworks */ = {
371 | isa = PBXShellScriptBuildPhase;
372 | buildActionMask = 2147483647;
373 | files = (
374 | );
375 | inputPaths = (
376 | );
377 | name = "[CP] Embed Pods Frameworks";
378 | outputPaths = (
379 | );
380 | runOnlyForDeploymentPostprocessing = 0;
381 | shellPath = /bin/sh;
382 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-frameworks.sh\"\n";
383 | showEnvVarsInLog = 0;
384 | };
385 | C6907585B2FAEA8147E749B2 /* [CP] Copy Pods Resources */ = {
386 | isa = PBXShellScriptBuildPhase;
387 | buildActionMask = 2147483647;
388 | files = (
389 | );
390 | inputPaths = (
391 | );
392 | name = "[CP] Copy Pods Resources";
393 | outputPaths = (
394 | );
395 | runOnlyForDeploymentPostprocessing = 0;
396 | shellPath = /bin/sh;
397 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-resources.sh\"\n";
398 | showEnvVarsInLog = 0;
399 | };
400 | /* End PBXShellScriptBuildPhase section */
401 |
402 | /* Begin PBXSourcesBuildPhase section */
403 | D946E93A1D41027200DB4FCF /* Sources */ = {
404 | isa = PBXSourcesBuildPhase;
405 | buildActionMask = 2147483647;
406 | files = (
407 | D946E9721D41039E00DB4FCF /* AppCoordinator.swift in Sources */,
408 | D946E9821D410AE600DB4FCF /* AuthenticationCoordinator.swift in Sources */,
409 | D946E9741D4103CE00DB4FCF /* AppState.swift in Sources */,
410 | D946E9461D41027200DB4FCF /* SecondViewController.swift in Sources */,
411 | D946E9841D410B0500DB4FCF /* SignInViewController.swift in Sources */,
412 | D922A4F51D465A6B002FF3E7 /* MainViewController.swift in Sources */,
413 | D946E9861D410B1500DB4FCF /* ForgotPasswordViewController.swift in Sources */,
414 | D946E9421D41027200DB4FCF /* AppDelegate.swift in Sources */,
415 | D946E97D1D41078A00DB4FCF /* CatalogCoordinator.swift in Sources */,
416 | D946E9441D41027200DB4FCF /* FirstViewController.swift in Sources */,
417 | );
418 | runOnlyForDeploymentPostprocessing = 0;
419 | };
420 | D946E9501D41027200DB4FCF /* Sources */ = {
421 | isa = PBXSourcesBuildPhase;
422 | buildActionMask = 2147483647;
423 | files = (
424 | D946E9591D41027200DB4FCF /* CorduxPrototypeTests.swift in Sources */,
425 | );
426 | runOnlyForDeploymentPostprocessing = 0;
427 | };
428 | D946E95B1D41027200DB4FCF /* Sources */ = {
429 | isa = PBXSourcesBuildPhase;
430 | buildActionMask = 2147483647;
431 | files = (
432 | D946E9641D41027200DB4FCF /* CorduxPrototypeUITests.swift in Sources */,
433 | );
434 | runOnlyForDeploymentPostprocessing = 0;
435 | };
436 | /* End PBXSourcesBuildPhase section */
437 |
438 | /* Begin PBXTargetDependency section */
439 | D946E9561D41027200DB4FCF /* PBXTargetDependency */ = {
440 | isa = PBXTargetDependency;
441 | target = D946E93D1D41027200DB4FCF /* CorduxPrototype */;
442 | targetProxy = D946E9551D41027200DB4FCF /* PBXContainerItemProxy */;
443 | };
444 | D946E9611D41027200DB4FCF /* PBXTargetDependency */ = {
445 | isa = PBXTargetDependency;
446 | target = D946E93D1D41027200DB4FCF /* CorduxPrototype */;
447 | targetProxy = D946E9601D41027200DB4FCF /* PBXContainerItemProxy */;
448 | };
449 | /* End PBXTargetDependency section */
450 |
451 | /* Begin PBXVariantGroup section */
452 | D946E94C1D41027200DB4FCF /* LaunchScreen.storyboard */ = {
453 | isa = PBXVariantGroup;
454 | children = (
455 | D946E94D1D41027200DB4FCF /* Base */,
456 | );
457 | name = LaunchScreen.storyboard;
458 | sourceTree = "";
459 | };
460 | /* End PBXVariantGroup section */
461 |
462 | /* Begin XCBuildConfiguration section */
463 | D946E9661D41027200DB4FCF /* Debug */ = {
464 | isa = XCBuildConfiguration;
465 | buildSettings = {
466 | ALWAYS_SEARCH_USER_PATHS = NO;
467 | CLANG_ANALYZER_NONNULL = YES;
468 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
469 | CLANG_CXX_LIBRARY = "libc++";
470 | CLANG_ENABLE_MODULES = YES;
471 | CLANG_ENABLE_OBJC_ARC = YES;
472 | CLANG_WARN_BOOL_CONVERSION = YES;
473 | CLANG_WARN_CONSTANT_CONVERSION = YES;
474 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
475 | CLANG_WARN_EMPTY_BODY = YES;
476 | CLANG_WARN_ENUM_CONVERSION = YES;
477 | CLANG_WARN_INFINITE_RECURSION = YES;
478 | CLANG_WARN_INT_CONVERSION = YES;
479 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
480 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
481 | CLANG_WARN_UNREACHABLE_CODE = YES;
482 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
483 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
484 | COPY_PHASE_STRIP = NO;
485 | DEBUG_INFORMATION_FORMAT = dwarf;
486 | ENABLE_STRICT_OBJC_MSGSEND = YES;
487 | ENABLE_TESTABILITY = YES;
488 | GCC_C_LANGUAGE_STANDARD = gnu99;
489 | GCC_DYNAMIC_NO_PIC = NO;
490 | GCC_NO_COMMON_BLOCKS = YES;
491 | GCC_OPTIMIZATION_LEVEL = 0;
492 | GCC_PREPROCESSOR_DEFINITIONS = (
493 | "DEBUG=1",
494 | "$(inherited)",
495 | );
496 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
497 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
498 | GCC_WARN_UNDECLARED_SELECTOR = YES;
499 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
500 | GCC_WARN_UNUSED_FUNCTION = YES;
501 | GCC_WARN_UNUSED_VARIABLE = YES;
502 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
503 | MTL_ENABLE_DEBUG_INFO = YES;
504 | ONLY_ACTIVE_ARCH = YES;
505 | SDKROOT = iphoneos;
506 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
507 | TARGETED_DEVICE_FAMILY = "1,2";
508 | };
509 | name = Debug;
510 | };
511 | D946E9671D41027200DB4FCF /* Release */ = {
512 | isa = XCBuildConfiguration;
513 | buildSettings = {
514 | ALWAYS_SEARCH_USER_PATHS = NO;
515 | CLANG_ANALYZER_NONNULL = YES;
516 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
517 | CLANG_CXX_LIBRARY = "libc++";
518 | CLANG_ENABLE_MODULES = YES;
519 | CLANG_ENABLE_OBJC_ARC = YES;
520 | CLANG_WARN_BOOL_CONVERSION = YES;
521 | CLANG_WARN_CONSTANT_CONVERSION = YES;
522 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
523 | CLANG_WARN_EMPTY_BODY = YES;
524 | CLANG_WARN_ENUM_CONVERSION = YES;
525 | CLANG_WARN_INFINITE_RECURSION = YES;
526 | CLANG_WARN_INT_CONVERSION = YES;
527 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
528 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
529 | CLANG_WARN_UNREACHABLE_CODE = YES;
530 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
531 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
532 | COPY_PHASE_STRIP = NO;
533 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
534 | ENABLE_NS_ASSERTIONS = NO;
535 | ENABLE_STRICT_OBJC_MSGSEND = YES;
536 | GCC_C_LANGUAGE_STANDARD = gnu99;
537 | GCC_NO_COMMON_BLOCKS = YES;
538 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
539 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
540 | GCC_WARN_UNDECLARED_SELECTOR = YES;
541 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
542 | GCC_WARN_UNUSED_FUNCTION = YES;
543 | GCC_WARN_UNUSED_VARIABLE = YES;
544 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
545 | MTL_ENABLE_DEBUG_INFO = NO;
546 | SDKROOT = iphoneos;
547 | TARGETED_DEVICE_FAMILY = "1,2";
548 | VALIDATE_PRODUCT = YES;
549 | };
550 | name = Release;
551 | };
552 | D946E9691D41027200DB4FCF /* Debug */ = {
553 | isa = XCBuildConfiguration;
554 | baseConfigurationReference = 54AB738FE12A7D14DACD3E78 /* Pods-CorduxPrototype.debug.xcconfig */;
555 | buildSettings = {
556 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
557 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
558 | INFOPLIST_FILE = CorduxPrototype/Info.plist;
559 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
560 | PRODUCT_BUNDLE_IDENTIFIER = com.willowtreeapps.CorduxPrototype;
561 | PRODUCT_NAME = "$(TARGET_NAME)";
562 | SWIFT_VERSION = 3.0;
563 | };
564 | name = Debug;
565 | };
566 | D946E96A1D41027200DB4FCF /* Release */ = {
567 | isa = XCBuildConfiguration;
568 | baseConfigurationReference = B2AF9DBFC9F31A01AD177704 /* Pods-CorduxPrototype.release.xcconfig */;
569 | buildSettings = {
570 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
571 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
572 | INFOPLIST_FILE = CorduxPrototype/Info.plist;
573 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
574 | PRODUCT_BUNDLE_IDENTIFIER = com.willowtreeapps.CorduxPrototype;
575 | PRODUCT_NAME = "$(TARGET_NAME)";
576 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
577 | SWIFT_VERSION = 3.0;
578 | };
579 | name = Release;
580 | };
581 | D946E96C1D41027200DB4FCF /* Debug */ = {
582 | isa = XCBuildConfiguration;
583 | buildSettings = {
584 | BUNDLE_LOADER = "$(TEST_HOST)";
585 | INFOPLIST_FILE = CorduxPrototypeTests/Info.plist;
586 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
587 | PRODUCT_BUNDLE_IDENTIFIER = com.willowtreeapps.CorduxPrototypeTests;
588 | PRODUCT_NAME = "$(TARGET_NAME)";
589 | SWIFT_VERSION = 3.0;
590 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CorduxPrototype.app/CorduxPrototype";
591 | };
592 | name = Debug;
593 | };
594 | D946E96D1D41027200DB4FCF /* Release */ = {
595 | isa = XCBuildConfiguration;
596 | buildSettings = {
597 | BUNDLE_LOADER = "$(TEST_HOST)";
598 | INFOPLIST_FILE = CorduxPrototypeTests/Info.plist;
599 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
600 | PRODUCT_BUNDLE_IDENTIFIER = com.willowtreeapps.CorduxPrototypeTests;
601 | PRODUCT_NAME = "$(TARGET_NAME)";
602 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
603 | SWIFT_VERSION = 3.0;
604 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CorduxPrototype.app/CorduxPrototype";
605 | };
606 | name = Release;
607 | };
608 | D946E96F1D41027200DB4FCF /* Debug */ = {
609 | isa = XCBuildConfiguration;
610 | buildSettings = {
611 | INFOPLIST_FILE = CorduxPrototypeUITests/Info.plist;
612 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
613 | PRODUCT_BUNDLE_IDENTIFIER = com.willowtreeapps.CorduxPrototypeUITests;
614 | PRODUCT_NAME = "$(TARGET_NAME)";
615 | SWIFT_VERSION = 3.0;
616 | TEST_TARGET_NAME = CorduxPrototype;
617 | };
618 | name = Debug;
619 | };
620 | D946E9701D41027200DB4FCF /* Release */ = {
621 | isa = XCBuildConfiguration;
622 | buildSettings = {
623 | INFOPLIST_FILE = CorduxPrototypeUITests/Info.plist;
624 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
625 | PRODUCT_BUNDLE_IDENTIFIER = com.willowtreeapps.CorduxPrototypeUITests;
626 | PRODUCT_NAME = "$(TARGET_NAME)";
627 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
628 | SWIFT_VERSION = 3.0;
629 | TEST_TARGET_NAME = CorduxPrototype;
630 | };
631 | name = Release;
632 | };
633 | /* End XCBuildConfiguration section */
634 |
635 | /* Begin XCConfigurationList section */
636 | D946E9391D41027200DB4FCF /* Build configuration list for PBXProject "CorduxPrototype" */ = {
637 | isa = XCConfigurationList;
638 | buildConfigurations = (
639 | D946E9661D41027200DB4FCF /* Debug */,
640 | D946E9671D41027200DB4FCF /* Release */,
641 | );
642 | defaultConfigurationIsVisible = 0;
643 | defaultConfigurationName = Release;
644 | };
645 | D946E9681D41027200DB4FCF /* Build configuration list for PBXNativeTarget "CorduxPrototype" */ = {
646 | isa = XCConfigurationList;
647 | buildConfigurations = (
648 | D946E9691D41027200DB4FCF /* Debug */,
649 | D946E96A1D41027200DB4FCF /* Release */,
650 | );
651 | defaultConfigurationIsVisible = 0;
652 | defaultConfigurationName = Release;
653 | };
654 | D946E96B1D41027200DB4FCF /* Build configuration list for PBXNativeTarget "CorduxPrototypeTests" */ = {
655 | isa = XCConfigurationList;
656 | buildConfigurations = (
657 | D946E96C1D41027200DB4FCF /* Debug */,
658 | D946E96D1D41027200DB4FCF /* Release */,
659 | );
660 | defaultConfigurationIsVisible = 0;
661 | defaultConfigurationName = Release;
662 | };
663 | D946E96E1D41027200DB4FCF /* Build configuration list for PBXNativeTarget "CorduxPrototypeUITests" */ = {
664 | isa = XCConfigurationList;
665 | buildConfigurations = (
666 | D946E96F1D41027200DB4FCF /* Debug */,
667 | D946E9701D41027200DB4FCF /* Release */,
668 | );
669 | defaultConfigurationIsVisible = 0;
670 | defaultConfigurationName = Release;
671 | };
672 | /* End XCConfigurationList section */
673 | };
674 | rootObject = D946E9361D41027200DB4FCF /* Project object */;
675 | }
676 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/AppCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppCoordinator.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cordux
11 |
12 | final class AppCoordinator: SceneCoordinator {
13 | enum RouteSegment: String, RouteConvertible {
14 | case auth
15 | case catalog
16 | }
17 |
18 | let store: Store
19 | let container: UIViewController
20 |
21 | var currentScene: Scene?
22 |
23 | var rootViewController: UIViewController {
24 | return container
25 | }
26 |
27 | init(store: Store, container: UIViewController) {
28 | self.store = store
29 | self.container = container
30 | }
31 |
32 | func start(route: Route?) {
33 | store.rootCoordinator = self
34 | }
35 |
36 | public func coordinatorForTag(_ tag: String) -> AnyCoordinator? {
37 | guard let segment = RouteSegment(rawValue: tag) else {
38 | return nil
39 | }
40 |
41 | switch segment {
42 | case .auth:
43 | return AuthenticationCoordinator(store: store)
44 | case .catalog:
45 | return CatalogContainerCoordinator(store: store, rootCoordinator: CatalogCoordinator(store: store))
46 | }
47 | }
48 |
49 | public func presentCoordinator(_ coordinator: AnyCoordinator?, completionHandler: @escaping () -> Void) {
50 | let old = container.childViewControllers.first
51 | guard let new = coordinator?.rootViewController else {
52 | completionHandler()
53 | return
54 | }
55 |
56 | old?.willMove(toParentViewController: nil)
57 | container.addChildViewController(new)
58 | container.view.addSubview(new.view)
59 |
60 | var constraints: [NSLayoutConstraint] = []
61 | constraints.append(new.view.leftAnchor.constraint(equalTo: container.view.leftAnchor))
62 | constraints.append(new.view.rightAnchor.constraint(equalTo: container.view.rightAnchor))
63 | constraints.append(new.view.topAnchor.constraint(equalTo: container.view.topAnchor))
64 | constraints.append(new.view.bottomAnchor.constraint(equalTo: container.view.bottomAnchor))
65 | NSLayoutConstraint.activate(constraints)
66 |
67 | new.view.alpha = 0
68 | UIView.animate(withDuration: 0.3, animations: {
69 | old?.view.alpha = 0
70 | new.view.alpha = 1
71 | }, completion: { _ in
72 | old?.view.removeFromSuperview()
73 | old?.removeFromParentViewController()
74 | new.didMove(toParentViewController: self.container)
75 | completionHandler()
76 | })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cordux
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 | var coordinator: AppCoordinator!
17 |
18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
19 | guard let mainController = window?.rootViewController as? MainViewController else {
20 | fatalError()
21 | }
22 |
23 | UIViewController.swizzleLifecycleDelegatingViewControllerMethods()
24 |
25 | var state = AppState()
26 | state.route = AppCoordinator.RouteSegment.auth.route()
27 | let store = Store(initialState: state, reducer: AppReducer(), middlewares: [ActionLogger()])
28 |
29 | coordinator = AppCoordinator(store: store, container: mainController)
30 | coordinator.start(route: state.route)
31 |
32 | return true
33 | }
34 |
35 | func applicationWillResignActive(_ application: UIApplication) {
36 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
37 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
38 | }
39 |
40 | func applicationDidEnterBackground(_ application: UIApplication) {
41 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
42 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
43 | }
44 |
45 | func applicationWillEnterForeground(_ application: UIApplication) {
46 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
47 | }
48 |
49 | func applicationDidBecomeActive(_ application: UIApplication) {
50 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
51 | }
52 |
53 | func applicationWillTerminate(_ application: UIApplication) {
54 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
55 | }
56 |
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/AppState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppState.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cordux
11 |
12 | typealias Store = Cordux.Store
13 |
14 | struct AppState: StateType {
15 | var route: Route = []
16 | var name: String = "Hello"
17 | var authenticationState: AuthenticationState = .unauthenticated
18 | }
19 |
20 | enum AuthenticationState {
21 | case unauthenticated
22 | case authenticated
23 | }
24 |
25 | enum AuthenticationAction: Action {
26 | case signIn
27 | case signOut
28 | }
29 |
30 | enum ModalAction: Action {
31 | case present
32 | case dismiss
33 | }
34 |
35 | struct Noop: Action {}
36 |
37 | final class AppReducer: Reducer {
38 | func handleAction(_ action: Action, state: AppState) -> AppState {
39 | var state = state
40 | state = reduceAuthentication(action, state: state)
41 | state = reduceModal(action, state: state)
42 | return state
43 | }
44 |
45 | func reduceAuthentication(_ action: Action, state: AppState) -> AppState {
46 | guard let action = action as? AuthenticationAction else {
47 | return state
48 | }
49 |
50 | var state = state
51 |
52 | switch action {
53 | case .signIn:
54 | state.route = ["catalog"]
55 | state.authenticationState = .authenticated
56 | case .signOut:
57 | state.route = ["auth"]
58 | state.authenticationState = .unauthenticated
59 | }
60 |
61 | return state
62 | }
63 |
64 | func reduceModal(_ action: Action, state: AppState) -> AppState {
65 | guard let action = action as? ModalAction else {
66 | return state
67 | }
68 |
69 | var state = state
70 |
71 | switch action {
72 | case .present:
73 | state.route = state.route.reduce(.push("modal"))
74 | case .dismiss:
75 | state.route = state.route.reduce(.pop("modal"))
76 | }
77 |
78 | return state
79 | }
80 | }
81 |
82 | final class ActionLogger: Middleware {
83 | func before(action: Action, state: AppState) {
84 | print("Before: \(action): \(state)")
85 | }
86 | func after(action: Action, state: AppState) {
87 | print("After: \(action): \(state)")
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/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 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Example/CorduxPrototype/Assets.xcassets/first.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "first.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/CorduxPrototype/Assets.xcassets/first.imageset/first.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/cordux/7b9ac675e008f0e4a6e7b404dba5aebe363e0c48/Example/CorduxPrototype/Assets.xcassets/first.imageset/first.pdf
--------------------------------------------------------------------------------
/Example/CorduxPrototype/Assets.xcassets/second.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "second.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/CorduxPrototype/Assets.xcassets/second.imageset/second.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/cordux/7b9ac675e008f0e4a6e7b404dba5aebe363e0c48/Example/CorduxPrototype/Assets.xcassets/second.imageset/second.pdf
--------------------------------------------------------------------------------
/Example/CorduxPrototype/Authentication.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
28 |
35 |
41 |
42 |
43 |
44 |
45 |
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 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/AuthenticationCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthenticationCoordinator.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cordux
11 |
12 | final class AuthenticationCoordinator: NavigationControllerCoordinator {
13 | enum RouteSegment: String, RouteConvertible {
14 | case signIn
15 | case fp
16 | }
17 |
18 | var store: Store
19 |
20 | let storyboard = UIStoryboard(name: "Authentication", bundle: nil)
21 | let navigationController: UINavigationController
22 |
23 | let signInViewController: SignInViewController
24 |
25 | init(store: Store) {
26 | self.store = store
27 |
28 | signInViewController = storyboard.instantiateInitialViewController() as! SignInViewController
29 | navigationController = UINavigationController(rootViewController: signInViewController)
30 | }
31 |
32 | func start(route: Route?) {
33 | signInViewController.inject(handler: self)
34 | signInViewController.corduxContext = Context(routeSegment: RouteSegment.signIn, lifecycleDelegate: self)
35 |
36 | let segments = parse(route: route)
37 | guard !segments.isEmpty else {
38 | store.setRoute(.push(RouteSegment.signIn))
39 | return
40 | }
41 |
42 | if segments.last == .fp {
43 | navigationController.pushViewController(createForgotPasswordViewController(), animated: false)
44 | }
45 | }
46 |
47 | public func updateRoute(_ route: Route, completionHandler: @escaping () -> Void) {
48 | guard parse(route: route).last == .fp else {
49 | completionHandler()
50 | return
51 | }
52 |
53 | navigationController.pushViewController(createForgotPasswordViewController(), animated: true, completion: completionHandler)
54 | }
55 | func updateRoute(_ route: Route) {
56 | if parse(route: route).last == .fp {
57 | navigationController.pushViewController(createForgotPasswordViewController(), animated: true)
58 | }
59 | }
60 |
61 | func parse(route: Route?) -> [RouteSegment] {
62 | return route?.flatMap({ RouteSegment.init(rawValue: $0) }) ?? []
63 | }
64 |
65 | func createForgotPasswordViewController() -> ForgotPasswordViewController {
66 | let forgotPasswordViewController = storyboard.instantiateViewController(withIdentifier: "ForgotPassword") as! ForgotPasswordViewController
67 | forgotPasswordViewController.inject(self)
68 | forgotPasswordViewController.corduxContext = Context(routeSegment: RouteSegment.fp, lifecycleDelegate: self)
69 | return forgotPasswordViewController
70 | }
71 | }
72 |
73 | extension AuthenticationCoordinator: ViewControllerLifecycleDelegate {
74 | @objc func viewDidLoad(viewController: UIViewController) {
75 | if viewController === signInViewController {
76 | store.subscribe(signInViewController, SignInViewModel.init)
77 | }
78 | }
79 |
80 | @objc func didMove(toParentViewController parentViewController: UIViewController?, viewController: UIViewController) {
81 | guard parentViewController == nil else {
82 | return
83 | }
84 |
85 | popRoute(viewController)
86 | }
87 | }
88 |
89 | extension AuthenticationCoordinator: SignInHandler {
90 | func signIn() {
91 | store.dispatch(AuthenticationAction.signIn)
92 | }
93 |
94 | func forgotPassword() {
95 | store.route(.push(RouteSegment.fp))
96 | }
97 | }
98 |
99 | extension SignInViewController: Renderer {}
100 |
101 | extension SignInViewModel {
102 | init?(_ state: AppState) {
103 | return nil
104 | //name = state.name
105 | }
106 | }
107 |
108 | extension AuthenticationCoordinator: ForgotPasswordHandler {
109 | func emailPassword() {
110 |
111 | }
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/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/CorduxPrototype/Catalog.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 |
45 |
46 |
47 |
48 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
91 |
92 |
93 |
94 |
101 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/CatalogCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CatalogCoordinator.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cordux
11 |
12 | final class CatalogContainerCoordinator: PresentingCoordinator {
13 | var presented: Scene?
14 |
15 | lazy var presentables: [GeneratingScene] = {
16 | return [GeneratingScene(tag: "modal") { return ModalCoordinator(store: self.store) }]
17 | }()
18 |
19 | var store: Store
20 | var rootCoordinator: AnyCoordinator
21 |
22 | init(store: Store, rootCoordinator: AnyCoordinator) {
23 | self.store = store
24 | self.rootCoordinator = rootCoordinator
25 | }
26 | }
27 |
28 | final class CatalogCoordinator: NSObject, TabBarControllerCoordinator {
29 | var store: Store
30 |
31 | let scenes: [Scene]
32 | let tabBarController: UITabBarController
33 |
34 | init(store: Store) {
35 | self.store = store
36 | scenes = [
37 | Scene(tag: "first", coordinator: FirstCoordinator(store: store)),
38 | Scene(tag: "second", coordinator: SecondCoordinator(store: store)),
39 | ]
40 |
41 | tabBarController = UIStoryboard(name: "Catalog", bundle: nil)
42 | .instantiateInitialViewController() as! UITabBarController
43 |
44 | tabBarController.viewControllers = scenes.map { $0.coordinator.rootViewController }
45 | }
46 |
47 | func start(route: Route?) {
48 | tabBarController.delegate = self
49 | scenes.forEach { $0.coordinator.start(route: []) }
50 | store.setRoute(.push(scenes[tabBarController.selectedIndex]))
51 | }
52 | }
53 |
54 | extension CatalogCoordinator: UITabBarControllerDelegate {
55 | func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
56 | return setRouteForViewController(viewController)
57 | }
58 | }
59 |
60 | final class FirstCoordinator: NavigationControllerCoordinator {
61 | var store: Store
62 |
63 | let navigationController: UINavigationController
64 | var rootViewController: UIViewController { return navigationController }
65 | let first: FirstViewController
66 |
67 | init(store: Store) {
68 | self.store = store
69 |
70 | first = FirstViewController.make()
71 | navigationController = UINavigationController(rootViewController: first)
72 | }
73 |
74 | func start(route: Route?) {
75 | first.inject(handler: self)
76 | }
77 |
78 | func updateRoute(_ route: Route, completionHandler: @escaping () -> Void) {
79 | completionHandler()
80 | }
81 | }
82 |
83 | extension FirstCoordinator: FirstHandler {
84 | func performAction() {
85 | store.dispatch(ModalAction.present)
86 | }
87 | }
88 |
89 | final class SecondCoordinator: NavigationControllerCoordinator {
90 | var store: Store
91 |
92 | let navigationController: UINavigationController
93 | var rootViewController: UIViewController { return navigationController }
94 | let second: SecondViewController
95 |
96 | init(store: Store) {
97 | self.store = store
98 |
99 | second = SecondViewController.make()
100 | navigationController = UINavigationController(rootViewController: second)
101 | }
102 |
103 | func start(route: Route?) {
104 | second.inject(handler: self)
105 | }
106 |
107 | func updateRoute(_ route: Route, completionHandler: @escaping () -> Void) {
108 | completionHandler()
109 | }
110 | }
111 |
112 | extension SecondCoordinator: SecondHandler {
113 | func performAction() {
114 | store.dispatch(Noop())
115 | }
116 |
117 | func signOut() {
118 | store.dispatch(AuthenticationAction.signOut)
119 | }
120 | }
121 |
122 | final class ModalCoordinator: Coordinator {
123 | var store: Store
124 |
125 | var route: Route = []
126 |
127 | var first: FirstViewController
128 | var rootViewController: UIViewController { return first }
129 |
130 | init(store: Store) {
131 | self.store = store
132 |
133 | first = FirstViewController.make()
134 | }
135 |
136 | func start(route: Route?) {
137 | first.inject(handler: self)
138 | }
139 |
140 | func prepareForRoute(_: Route?, completionHandler: @escaping () -> Void) {
141 | completionHandler()
142 | }
143 |
144 | func setRoute(_: Route?, completionHandler: @escaping () -> Void) {
145 | completionHandler()
146 | }
147 | }
148 |
149 | extension ModalCoordinator: FirstHandler {
150 | func performAction() {
151 | store.dispatch(ModalAction.dismiss)
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/FirstViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FirstViewController.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol FirstHandler: class {
12 | func performAction()
13 | }
14 |
15 | class FirstViewController: UIViewController {
16 | weak var handler: FirstHandler?
17 |
18 | static func make() -> FirstViewController {
19 | let storyboard = UIStoryboard(name: "Catalog", bundle: nil)
20 | return storyboard.instantiateViewController(withIdentifier: "First") as! FirstViewController
21 | }
22 |
23 | func inject(handler: FirstHandler) {
24 | self.handler = handler
25 | }
26 |
27 | @IBAction func performAction(_ sender: AnyObject) {
28 | handler?.performAction()
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/ForgotPasswordViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForgotPasswordViewController.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol ForgotPasswordHandler {
12 | func emailPassword()
13 | }
14 |
15 | class ForgotPasswordViewController: UIViewController {
16 |
17 | var handler: ForgotPasswordHandler!
18 |
19 | func inject(_ handler: ForgotPasswordHandler) {
20 | self.handler = handler
21 | }
22 |
23 | @IBAction func emailPassword(_ sender: AnyObject) {
24 | handler.emailPassword()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UIStatusBarTintParameters
34 |
35 | UINavigationBar
36 |
37 | Style
38 | UIBarStyleDefault
39 | Translucent
40 |
41 |
42 |
43 | UISupportedInterfaceOrientations
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UISupportedInterfaceOrientations~ipad
50 |
51 | UIInterfaceOrientationPortrait
52 | UIInterfaceOrientationPortraitUpsideDown
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/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 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/MainViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/25/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MainViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | // Do any additional setup after loading the view.
17 | }
18 |
19 | override func didReceiveMemoryWarning() {
20 | super.didReceiveMemoryWarning()
21 | // Dispose of any resources that can be recreated.
22 | }
23 |
24 |
25 | /*
26 | // MARK: - Navigation
27 |
28 | // In a storyboard-based application, you will often want to do a little preparation before navigation
29 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
30 | // Get the new view controller using segue.destinationViewController.
31 | // Pass the selected object to the new view controller.
32 | }
33 | */
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/SecondViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecondViewController.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol SecondHandler: class {
12 | func performAction()
13 | func signOut()
14 | }
15 |
16 | class SecondViewController: UIViewController {
17 | weak var handler: SecondHandler?
18 |
19 | static func make() -> SecondViewController {
20 | let storyboard = UIStoryboard(name: "Catalog", bundle: nil)
21 | return storyboard.instantiateViewController(withIdentifier: "Second") as! SecondViewController
22 | }
23 |
24 |
25 | func inject(handler: SecondHandler) {
26 | self.handler = handler
27 | }
28 |
29 | @IBAction func performAction(_ sender: AnyObject) {
30 | handler?.performAction()
31 | }
32 |
33 | @IBAction func signOut(_ sender: AnyObject) {
34 | handler?.signOut()
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/Example/CorduxPrototype/SignInViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignInViewController.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct SignInViewModel {
12 | let name: String
13 | }
14 |
15 | protocol SignInHandler {
16 | func signIn()
17 | func forgotPassword()
18 | }
19 |
20 | final class SignInViewController: UIViewController {
21 |
22 | @IBOutlet var nameLabel: UILabel!
23 |
24 | var handler: SignInHandler!
25 |
26 | func inject(handler: SignInHandler) {
27 | self.handler = handler
28 | }
29 |
30 | func render(_ viewModel: SignInViewModel?) {
31 | nameLabel?.text = viewModel?.name
32 | }
33 |
34 | @IBAction func signIn(_ sender: AnyObject) {
35 | handler.signIn()
36 | }
37 |
38 | @IBAction func forgotPassword(_ sender: AnyObject) {
39 | handler.forgotPassword()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Example/CorduxPrototypeTests/CorduxPrototypeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CorduxPrototypeTests.swift
3 | // CorduxPrototypeTests
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import CorduxPrototype
11 |
12 | class CorduxPrototypeTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Example/CorduxPrototypeTests/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 |
--------------------------------------------------------------------------------
/Example/CorduxPrototypeUITests/CorduxPrototypeUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CorduxPrototypeUITests.swift
3 | // CorduxPrototypeUITests
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class CorduxPrototypeUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | XCUIApplication().launch()
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testExample() {
32 | // Use recording to get started writing UI tests.
33 | // Use XCTAssert and related functions to verify your tests produce the correct results.
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Example/CorduxPrototypeUITests/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 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 | platform :ios, '9.0'
3 | inhibit_all_warnings!
4 |
5 | target 'CorduxPrototype' do
6 | pod 'Cordux', path: '..'
7 | end
8 |
9 | post_install do |installer|
10 | installer.pods_project.targets.each do |target|
11 | target.build_configurations.each do |config|
12 | config.build_settings['SWIFT_VERSION'] = '3.0'
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Cordux (0.1.7)
3 |
4 | DEPENDENCIES:
5 | - Cordux (from `..`)
6 |
7 | EXTERNAL SOURCES:
8 | Cordux:
9 | :path: ".."
10 |
11 | SPEC CHECKSUMS:
12 | Cordux: 9c9c485f0aa845c8c54679f31ccaa3c58a9fb56e
13 |
14 | PODFILE CHECKSUM: 284a10d3aecab71ef0d599dc2c45c053852d04a7
15 |
16 | COCOAPODS: 1.0.1
17 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/Cordux.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Cordux",
3 | "version": "0.1.7",
4 | "summary": "App Coordinators & Redux. A framework for UIKit development.",
5 | "description": "Cordux combines app coordiantors with a redux-like architecture.\n\nThis allows rapid and powerful UI development with full support\nfor routing and deep linking.",
6 | "homepage": "http://github.com/willowtreeapps/cordux",
7 | "license": "MIT",
8 | "authors": {
9 | "Ian Terrell": "ian.terrell@gmail.com"
10 | },
11 | "source": {
12 | "git": "https://github.com/willowtreeapps/cordux.git",
13 | "tag": "v0.1.7"
14 | },
15 | "platforms": {
16 | "ios": "9.0",
17 | "tvos": "9.1"
18 | },
19 | "source_files": "Sources/*.swift"
20 | }
21 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Cordux (0.1.7)
3 |
4 | DEPENDENCIES:
5 | - Cordux (from `..`)
6 |
7 | EXTERNAL SOURCES:
8 | Cordux:
9 | :path: ".."
10 |
11 | SPEC CHECKSUMS:
12 | Cordux: 9c9c485f0aa845c8c54679f31ccaa3c58a9fb56e
13 |
14 | PODFILE CHECKSUM: 284a10d3aecab71ef0d599dc2c45c053852d04a7
15 |
16 | COCOAPODS: 1.0.1
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Cordux/Cordux-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Cordux : NSObject
3 | @end
4 | @implementation PodsDummy_Cordux
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Cordux/Cordux-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #endif
4 |
5 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Cordux/Cordux-umbrella.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 |
4 | FOUNDATION_EXPORT double CorduxVersionNumber;
5 | FOUNDATION_EXPORT const unsigned char CorduxVersionString[];
6 |
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Cordux/Cordux.modulemap:
--------------------------------------------------------------------------------
1 | framework module Cordux {
2 | umbrella header "Cordux-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Cordux/Cordux.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Cordux
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
5 | PODS_BUILD_DIR = $BUILD_DIR
6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
9 | SKIP_INSTALL = YES
10 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Cordux/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 | 0.1.7
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/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.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Cordux
5 |
6 | MIT License
7 |
8 | Copyright (c) 2016 WillowTree, Inc.
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 | Generated by CocoaPods - https://cocoapods.org
29 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | MIT License
18 |
19 | Copyright (c) 2016 WillowTree, Inc.
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy
22 | of this software and associated documentation files (the "Software"), to deal
23 | in the Software without restriction, including without limitation the rights
24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | copies of the Software, and to permit persons to whom the Software is
26 | furnished to do so, subject to the following conditions:
27 |
28 | The above copyright notice and this permission notice shall be included in all
29 | copies or substantial portions of the Software.
30 |
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 | SOFTWARE.
38 |
39 | Title
40 | Cordux
41 | Type
42 | PSGroupSpecifier
43 |
44 |
45 | FooterText
46 | Generated by CocoaPods - https://cocoapods.org
47 | Title
48 |
49 | Type
50 | PSGroupSpecifier
51 |
52 |
53 | StringsTable
54 | Acknowledgements
55 | Title
56 | Acknowledgements
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_CorduxPrototype : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_CorduxPrototype
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
6 |
7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
8 |
9 | install_framework()
10 | {
11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
12 | local source="${BUILT_PRODUCTS_DIR}/$1"
13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
15 | elif [ -r "$1" ]; then
16 | local source="$1"
17 | fi
18 |
19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
20 |
21 | if [ -L "${source}" ]; then
22 | echo "Symlinked..."
23 | source="$(readlink "${source}")"
24 | fi
25 |
26 | # use filter instead of exclude so missing patterns dont' throw errors
27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
29 |
30 | local basename
31 | basename="$(basename -s .framework "$1")"
32 | binary="${destination}/${basename}.framework/${basename}"
33 | if ! [ -r "$binary" ]; then
34 | binary="${destination}/${basename}"
35 | fi
36 |
37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
39 | strip_invalid_archs "$binary"
40 | fi
41 |
42 | # Resign the code if required by the build settings to avoid unstable apps
43 | code_sign_if_enabled "${destination}/$(basename "$1")"
44 |
45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
47 | local swift_runtime_libs
48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
49 | for lib in $swift_runtime_libs; do
50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
52 | code_sign_if_enabled "${destination}/${lib}"
53 | done
54 | fi
55 | }
56 |
57 | # Signs a framework with the provided identity
58 | code_sign_if_enabled() {
59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
60 | # Use the current code_sign_identitiy
61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
62 | echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
63 | /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
64 | fi
65 | }
66 |
67 | # Strip invalid architectures
68 | strip_invalid_archs() {
69 | binary="$1"
70 | # Get architectures for current file
71 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
72 | stripped=""
73 | for arch in $archs; do
74 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
75 | # Strip non-valid architectures in-place
76 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
77 | stripped="$stripped $arch"
78 | fi
79 | done
80 | if [[ "$stripped" ]]; then
81 | echo "Stripped $binary of architectures:$stripped"
82 | fi
83 | }
84 |
85 |
86 | if [[ "$CONFIGURATION" == "Debug" ]]; then
87 | install_framework "$BUILT_PRODUCTS_DIR/Cordux/Cordux.framework"
88 | fi
89 | if [[ "$CONFIGURATION" == "Release" ]]; then
90 | install_framework "$BUILT_PRODUCTS_DIR/Cordux/Cordux.framework"
91 | fi
92 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | case "${TARGETED_DEVICE_FAMILY}" in
12 | 1,2)
13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
14 | ;;
15 | 1)
16 | TARGET_DEVICE_ARGS="--target-device iphone"
17 | ;;
18 | 2)
19 | TARGET_DEVICE_ARGS="--target-device ipad"
20 | ;;
21 | *)
22 | TARGET_DEVICE_ARGS="--target-device mac"
23 | ;;
24 | esac
25 |
26 | realpath() {
27 | DIRECTORY="$(cd "${1%/*}" && pwd)"
28 | FILENAME="${1##*/}"
29 | echo "$DIRECTORY/$FILENAME"
30 | }
31 |
32 | install_resource()
33 | {
34 | if [[ "$1" = /* ]] ; then
35 | RESOURCE_PATH="$1"
36 | else
37 | RESOURCE_PATH="${PODS_ROOT}/$1"
38 | fi
39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
40 | cat << EOM
41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
42 | EOM
43 | exit 1
44 | fi
45 | case $RESOURCE_PATH in
46 | *.storyboard)
47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
49 | ;;
50 | *.xib)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.framework)
55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
59 | ;;
60 | *.xcdatamodel)
61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
63 | ;;
64 | *.xcdatamodeld)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
67 | ;;
68 | *.xcmappingmodel)
69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
71 | ;;
72 | *.xcassets)
73 | ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH")
74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
75 | ;;
76 | *)
77 | echo "$RESOURCE_PATH"
78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
79 | ;;
80 | esac
81 | }
82 |
83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | fi
89 | rm -f "$RESOURCES_TO_COPY"
90 |
91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
92 | then
93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
95 | while read line; do
96 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then
97 | XCASSET_FILES+=("$line")
98 | fi
99 | done <<<"$OTHER_XCASSETS"
100 |
101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
102 | fi
103 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype-umbrella.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 |
4 | FOUNDATION_EXPORT double Pods_CorduxPrototypeVersionNumber;
5 | FOUNDATION_EXPORT const unsigned char Pods_CorduxPrototypeVersionString[];
6 |
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype.debug.xcconfig:
--------------------------------------------------------------------------------
1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Cordux"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Cordux/Cordux.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Cordux"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = $BUILD_DIR
9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_ROOT = ${SRCROOT}/Pods
11 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_CorduxPrototype {
2 | umbrella header "Pods-CorduxPrototype-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-CorduxPrototype/Pods-CorduxPrototype.release.xcconfig:
--------------------------------------------------------------------------------
1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Cordux"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Cordux/Cordux.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Cordux"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = $BUILD_DIR
9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_ROOT = ${SRCROOT}/Pods
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 WillowTree, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cordux
2 |
3 | [](https://github.com/Carthage/Carthage)
4 |
5 |
6 |
7 | ## Installation
8 |
9 | CocoaPods:
10 |
11 | ```ruby
12 | pod 'Cordux'
13 | ```
14 |
15 | Carthage:
16 |
17 | ```ruby
18 | github "willowtreeapps/cordux" >= 0.1
19 | ```
20 |
21 | ## Cordux combines app coordinators with Redux
22 |
23 | Soroush Khanlou's [blog post](http://khanlou.com/2015/10/coordinators-redux/)
24 | gives a great rationale for and explanation of app coordinators. This project
25 | combines those ideas with a Redux-like architecture, which you can read about
26 | on the [ReSwift](https://github.com/ReSwift/ReSwift) repository.
27 |
28 | Combining the two natively leads to a lot of advantages:
29 |
30 | * View controllers become even simpler
31 | * Action creator methods clearly live in app coordinators
32 | * Navigating via route becomes simpler
33 | * Normal navigation and routing share code paths
34 | * Deep linking becomes almost trivial
35 |
36 | In this model, view controllers have exactly two responsibilities:
37 |
38 | 1. Render latest state
39 | 2. Convert UI interaction into user intents
40 |
41 | For example...
42 |
43 | ```swift
44 | struct ProductViewModel {
45 | let name: String
46 | let sku: String
47 | let price: Double
48 | }
49 |
50 | protocol ProductViewControllerHandler: class {
51 | func purchase(sku: String)
52 | }
53 |
54 | final class ProductViewController: UIViewController {
55 | @IBOutlet var nameLabel: UILabel!
56 |
57 | weak var handler: ProductViewControllerHandler!
58 | var viewModel: ProductViewModel!
59 |
60 | func inject(handler handler: ProductViewControllerHandler) {
61 | self.handler = handler
62 | }
63 |
64 | func render(viewModel: ProductViewModel) {
65 | self.viewModel = viewModel
66 | nameLabel.text = viewModel.name
67 | }
68 |
69 | @IBAction func purchaseButtonTapped(sender: AnyObject) {
70 | handler.purchase(viewModel.sku)
71 | }
72 | }
73 | ```
74 |
75 | ## Usage
76 |
77 | To use Cordux, you will need to create app coordinators for each main "scene" in
78 | your app. For example coordinators, please see the included
79 | [sample app](https://github.com/willowtreeapps/cordux/tree/develop/Example).
80 |
81 | ## Alternatives
82 |
83 | The biggest win that Cordux provides is in routing. If your app does not need
84 | to manage route via state, or navigate via route, it may make more sense to
85 | implement your own app coordinators and use ReSwift for your state management.
86 |
87 | ## Goals & Non-Goals
88 |
89 | *Goals*
90 |
91 | The primary goal is to simplify app level code with robust framework code. The
92 | following are the main areas of interest:
93 |
94 | * Call point API
95 | * Navigation code
96 | * Routing code
97 | * Subscriptions to the store
98 | * View controller lifecycle needs
99 |
100 | *Non-Goals*
101 |
102 | * Time travel
103 | * State restoration
104 | * State and action serialization
105 | * Supporting additional strategies for asynchronous work or side effects
106 |
107 | ## Roadmap
108 |
109 | Production level apps at WillowTree are currently being developed with Cordux.
110 | By the end of 2016 we hope to have the needs of this framework figured out.
111 |
112 | If ReSwift adopts our routing needs, Cordux may become a ReSwift extension.
113 |
114 | ## Code of Conduct
115 |
116 | Please read our [code of conduct](https://github.com/willowtreeapps/cordux/blob/develop/CODE_OF_CONDUCT.md)
117 | before participating. Please report any violations to
118 | [open.source.conduct@willowtreeapps.com](mailto:open.source.conduct@willowtreeapps.com.).
119 |
120 | ## Contributing
121 |
122 | Please read our
123 | [contributing guidelines](https://github.com/willowtreeapps/cordux/blob/develop/CONTRIBUTING.md)
124 | before contributing. Included are directions for opening issues, coding
125 | standards, and notes on development.
126 |
127 | Beyond contributing to the main code base, documentation and unit tests are
128 | always welcome contributions.
129 |
--------------------------------------------------------------------------------
/Sources/Context.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewControllerContext.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol Contextual: class {
12 | var corduxContext: Context? { get }
13 | }
14 |
15 | public final class Context: NSObject {
16 | public let routeSegment: RouteConvertible?
17 | var lifecycleDelegate: LifecycleDelegateCollection
18 |
19 | public init(routeSegment: RouteConvertible? = nil, lifecycleDelegate: ViewControllerLifecycleDelegate? = nil) {
20 | self.routeSegment = routeSegment
21 | self.lifecycleDelegate = LifecycleDelegateCollection()
22 |
23 | if let delegate = lifecycleDelegate {
24 | self.lifecycleDelegate.append(delegate)
25 | }
26 | }
27 | }
28 |
29 | extension UIViewController: Contextual {
30 | private struct ViewControllerKeys {
31 | static var Context = "cordux_context"
32 | }
33 |
34 | public var corduxContext: Context? {
35 | get {
36 | return objc_getAssociatedObject(self, &ViewControllerKeys.Context) as? Context
37 | }
38 |
39 | set {
40 | if let newValue = newValue {
41 | objc_setAssociatedObject(
42 | self,
43 | &ViewControllerKeys.Context,
44 | newValue as Context?,
45 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC
46 | )
47 | }
48 | }
49 | }
50 |
51 | public func addLifecycleDelegate(_ delegate: ViewControllerLifecycleDelegate) {
52 | guard let context = corduxContext else {
53 | corduxContext = Context(lifecycleDelegate: delegate)
54 | return
55 | }
56 |
57 | context.lifecycleDelegate.append(delegate)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/Coordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Coordinators.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// Type erased protocol for coordinators.
12 | public protocol AnyCoordinator: class {
13 |
14 | /// The current route that the coordinator is managing.
15 | ///
16 | /// This property should return the current route for the current view controller hierarchy
17 | /// from the point of view of this coordinator.
18 | var route: Route { get }
19 |
20 | /// This method is called before `prepareForRoute(_:completionHandler:)` with the same route to determine if that
21 | /// method needs called.
22 | ///
23 | /// A `route` of `nil` is used to indicate that the coordinator's root view controller will be removed from the
24 | /// hierarchy. If the coordinator is managing a view controller that could interfere with routing, such as a popover
25 | /// or a modal, then it should return true to tell the system that it needs to do so.
26 | ///
27 | /// - Parameters:
28 | /// - _: the `Route` that will be prepared for in `prepareForRoute(_:completionHandler:)` following this call
29 | func needsToPrepareForRoute(_: Route?) -> Bool
30 |
31 | /// This method is called before `setRoute(_:completionHandler:)` and with the same values, but only when
32 | /// `needsToPrepareForRoute(_:)` returns true.
33 | ///
34 | /// A `route` of `nil` is used to indicate that the coordinator's root view controller will be removed from the
35 | /// hierarchy. If the coordinator is managing a view controller that could interfere with routing, such as a popover
36 | /// or a modal, then it should reset the view controller state to one that can be properly routed.
37 | ///
38 | /// When the view controller state has been made ready for routing, `completionHandler` must be called.
39 | ///
40 | /// - Parameters:
41 | /// - _: the `Route` that will be routed to in `setRoute(_:completionHandler:)` following this call
42 | /// - completionHandler: this closure must be called when the view controller hierarchy has been prepared for
43 | /// routing
44 | func prepareForRoute(_: Route?, completionHandler: @escaping () -> Void)
45 |
46 | /// When called, the coordinator should adjust the view controller hierarchy to what is
47 | /// desired by the `Route`. When the navigation has been completed, `completionHandler` must be called.
48 | ///
49 | /// - Parameters:
50 | /// - _: the `Route` that should be navigated to
51 | /// - completionHandler: this closure must be called when the routing is completed
52 | func setRoute(_: Route?, completionHandler: @escaping () -> Void)
53 |
54 | /// Start the coordinator at the current route. A route of nil is used to indicate a coordinator for which
55 | /// its view controller is not intended to be part of the current view hierarchy.
56 | ///
57 | /// If the coordinator needs to adjust the route from what's given, it should call store.setRoute(_:)
58 | /// during this method. This may occur when the route is empty, and the coordinator wishes
59 | /// to set up initial state according to its own internal logic.
60 | ///
61 | /// Otherwise, if the route is complete, it should set up the view controller
62 | /// hierarchy accordingly.
63 | ///
64 | /// In all cases, the rootViewController should be ready to be displayed after this method
65 | /// is called.
66 | func start(route: Route?)
67 |
68 | /// The root view controller of the hierarchy managed by this coordinator.
69 | var rootViewController: UIViewController { get }
70 | }
71 |
72 | public protocol Coordinator: AnyCoordinator {
73 | associatedtype State: StateType
74 | var store: Store { get }
75 | }
76 |
77 | public func wrapPresentingCompletionHandler(coordinator: AnyCoordinator, completionHandler: @escaping () -> Void) -> () -> Void {
78 | return {
79 | guard let coordinator = coordinator as? AnyPresentingCoordinator, coordinator.hasStoredPresentable else {
80 | completionHandler()
81 | return
82 | }
83 |
84 | coordinator.presentStoredPresentable(completionHandler: completionHandler)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Sources/Logging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 8/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public typealias RouteLogger = (RouteEvent)->()
12 |
13 | /// A routing event that can be logged
14 | public enum RouteEvent {
15 | /// Route was updated directly by app code
16 | case set(Route)
17 |
18 | /// Route was updated by a routing action handled by the store
19 | case store(Route)
20 |
21 | /// Route was updated by the app's reducer
22 | case reducer(Route)
23 | }
24 |
25 | public func ConsoleRouteLogger(event: RouteEvent) {
26 | switch event {
27 | case .set(let route):
28 | log(route: route, annotation: "(set) ")
29 | case .store(let route):
30 | log(route: route, annotation: "(store) ")
31 | case .reducer(let route):
32 | log(route: route, annotation: "(reducer)")
33 | }
34 | }
35 |
36 | #if swift(>=3)
37 | private func log(route: Route, annotation: String) {
38 | print("ROUTE \(annotation): \(route.components.joined(separator: "/"))")
39 | }
40 | #else
41 | private func log(route route: Route, annotation: String) {
42 | print("ROUTE \(annotation): \(route.components.joinWithSeparator("/"))")
43 | }
44 | #endif
45 |
--------------------------------------------------------------------------------
/Sources/Middleware.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Middleware.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 8/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | #if swift(>=3)
12 | public protocol AnyMiddleware {
13 | func _before(action: Action, state: StateType)
14 | func _after(action: Action, state: StateType)
15 | }
16 |
17 | public protocol Middleware: AnyMiddleware {
18 | associatedtype State
19 | func before(action: Action, state: State)
20 | func after(action: Action, state: State)
21 | }
22 |
23 | public extension Middleware {
24 | func _before(action: Action, state: StateType) {
25 | withSpecificTypes(action, state: state, function: before)
26 | }
27 | func _after(action: Action, state: StateType) {
28 | withSpecificTypes(action, state: state, function: after)
29 | }
30 | }
31 | #else
32 | public protocol AnyMiddleware {
33 | func _before(action action: Action, state: StateType)
34 | func _after(action action: Action, state: StateType)
35 | }
36 |
37 | public protocol Middleware: AnyMiddleware {
38 | associatedtype State
39 | func before(action action: Action, state: State)
40 | func after(action action: Action, state: State)
41 | }
42 |
43 | public extension Middleware {
44 | func _before(action action: Action, state: StateType) {
45 | withSpecificTypes(action, state: state, function: before)
46 | }
47 | func _after(action: Action, state: StateType) {
48 | withSpecificTypes(action, state: state, function: after)
49 | }
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/Sources/NavigationControllerCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationControllerCoordinator.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 7/28/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol NavigationControllerCoordinator: Coordinator {
12 | var navigationController: UINavigationController { get }
13 | func updateRoute(_ route: Route, completionHandler: @escaping () -> Void)
14 | }
15 |
16 | public extension NavigationControllerCoordinator {
17 | public var rootViewController: UIViewController { return navigationController }
18 |
19 | public var route: Route {
20 | var route: Route = []
21 | navigationController.viewControllers.forEach { vc in
22 | #if swift(>=3)
23 | route.append(contentsOf: vc.corduxContext?.routeSegment?.route() ?? [])
24 | #else
25 | route.appendContentsOf(vc.corduxContext?.routeSegment?.route() ?? [])
26 | #endif
27 | }
28 | return route
29 | }
30 |
31 | public func needsToPrepareForRoute(_ route: Route?) -> Bool {
32 | return false
33 | }
34 |
35 | public func prepareForRoute(_ route: Route?, completionHandler: @escaping () -> Void) {
36 | completionHandler()
37 | }
38 |
39 | public func setRoute(_ newRoute: Route?, completionHandler: @escaping () -> Void) {
40 | guard let newRoute = newRoute, newRoute != route else {
41 | completionHandler()
42 | return
43 | }
44 |
45 | updateRoute(newRoute, completionHandler: completionHandler)
46 | }
47 |
48 | public func popRoute(_ viewController: UIViewController) {
49 | guard let routeSegment = viewController.corduxContext?.routeSegment else {
50 | return
51 | }
52 |
53 | store.setRoute(.pop(routeSegment.route()))
54 | }
55 | }
56 |
57 | extension UINavigationController {
58 | public func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
59 | pushViewController(viewController, animated: animated)
60 | animateWithCompletion(animated: animated, completion: completion)
61 | }
62 |
63 | public func popViewController(animated: Bool, completion: @escaping () -> Void) {
64 | popViewController(animated: animated)
65 | animateWithCompletion(animated: animated, completion: completion)
66 | }
67 |
68 | public func popToViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
69 | popToViewController(viewController, animated: animated)
70 | animateWithCompletion(animated: animated, completion: completion)
71 | }
72 |
73 | @discardableResult
74 | public func popToRootViewController(animated: Bool, completion: @escaping () -> Void) -> [UIViewController]? {
75 | let controllers = popToRootViewController(animated: animated)
76 | animateWithCompletion(animated: animated, completion: completion)
77 | return controllers
78 | }
79 |
80 | public func setViewControllers(_ viewControllers: [UIViewController], animated: Bool, completion: @escaping () -> Void) {
81 | setViewControllers(viewControllers, animated: animated)
82 | animateWithCompletion(animated: animated, completion: completion)
83 | }
84 |
85 | func animateWithCompletion(animated: Bool, completion: @escaping () -> Void) {
86 | guard animated, let coordinator = transitionCoordinator else {
87 | completion()
88 | return
89 | }
90 |
91 | coordinator.animate(alongsideTransition: nil) { _ in completion() }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Sources/NavigationControllerMetaCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationControllerMetaCoordinator.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 11/18/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// A coordinator to manage a navigation controller where each child view controller is managed by a single coordinator.
12 | ///
13 | /// This has some limitations:
14 | /// - One controller per coordinator
15 | /// - Routes for each child coordinator are fixed
16 | /// - This coordinator does not rearrange children; only pushes and pops
17 | /// - All child coordinators that are disappearing have `prepareForRoute(nil)` called simultaneously.
18 | ///
19 | /// Implementing classes should define the `ViewControllerLifecycleDelegate` method
20 | /// `didMove(toParentViewController:viewController:` to pop off coordinators when necessary. This logic should suffice:
21 | /**
22 | ```
23 | func didMove(toParentViewController parent: UIViewController?, viewController: UIViewController) {
24 | if parent == nil {
25 | coordinators.removeLast()
26 | }
27 | }
28 | ```
29 | */
30 | ///
31 | public protocol NavigationControllerMetaCoordinator: Coordinator, ViewControllerLifecycleDelegate {
32 | var navigationController: UINavigationController { get }
33 |
34 | var coordinators: [AnyCoordinator] { get set }
35 |
36 | func coordinators(for route: Route) -> [AnyCoordinator]
37 | }
38 |
39 | public extension NavigationControllerMetaCoordinator {
40 | public var rootViewController: UIViewController { return navigationController }
41 |
42 | public var route: Route {
43 | var route: Route = []
44 | coordinators.forEach { coordinator in
45 | #if swift(>=3)
46 | route.append(contentsOf: coordinator.route)
47 | #else
48 | route.appendContentsOf(coordinator.route)
49 | #endif
50 | }
51 | return route
52 | }
53 |
54 | /// - note: This method always returns false and child coordinators are not queried.
55 | public func needsToPrepareForRoute(_ route: Route?) -> Bool {
56 | return false
57 | }
58 |
59 | /// - note: The completion handler is called immediately, so all leaving coordinators can only reasonably
60 | /// prepare for nil routes synchronously.
61 | public func prepareForRoute(_ route: Route?, completionHandler: @escaping () -> Void) {
62 | let number = numberOfLastExistingCoordinator(for: route)
63 | for i in number.. Void) {
70 | guard let route = route, route != self.route else {
71 | completionHandler()
72 | return
73 | }
74 |
75 | let number = numberOfLastExistingCoordinator(for: route)
76 | let numberToRemove = coordinators.count - number
77 | coordinators.removeLast(numberToRemove)
78 | var viewControllers = navigationController.viewControllers
79 | viewControllers.removeLast(numberToRemove)
80 |
81 | var newRouteTail = route
82 | for i in 0.. Int {
98 | guard var route = route else {
99 | return 0
100 | }
101 |
102 | var index = -1
103 | for (i, coordinator) in coordinators.enumerated() {
104 | if route.isPrefixed(with: coordinator.route) {
105 | index = i
106 | route = Route(route.suffix(from: coordinator.route.components.count))
107 | } else {
108 | return index + 1
109 | }
110 | }
111 | return index + 1
112 | }
113 |
114 | public func popRoute(_ viewController: UIViewController) {
115 | guard let routeSegment = viewController.corduxContext?.routeSegment else {
116 | return
117 | }
118 |
119 | store.setRoute(.pop(routeSegment.route()))
120 | }
121 | }
122 |
123 |
--------------------------------------------------------------------------------
/Sources/PresentingCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentingCoordinator.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 11/2/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol AnyPresentingCoordinator {
12 | var hasStoredPresentable: Bool { get }
13 | func presentStoredPresentable(completionHandler: @escaping () -> Void)
14 | }
15 |
16 | public protocol PresentingCoordinator: Coordinator, AnyPresentingCoordinator, ViewControllerLifecycleDelegate {
17 | var rootCoordinator: AnyCoordinator { get }
18 | var presented: Scene? { get set }
19 |
20 | var presentables: [GeneratingScene] { get }
21 | func parsePresentableRoute(_ route: Route) -> (rootRoute: Route, presentable: (route: Route, scene: GeneratingScene)?)
22 |
23 | func present(scene: GeneratingScene, route: Route, completionHandler: @escaping () -> Void)
24 | func dismiss(completionHandler: @escaping () -> Void)
25 | }
26 |
27 | public extension PresentingCoordinator {
28 | public var rootViewController: UIViewController {
29 | return rootCoordinator.rootViewController
30 | }
31 |
32 | public var route: Route {
33 | var route: Route = []
34 | #if swift(>=3)
35 | route.append(contentsOf: rootCoordinator.route)
36 | #else
37 | route.appendContentsOf(rootCoordinator.route)
38 | #endif
39 | if let presented = presented, isPresentedViewControllerUpToDate {
40 | #if swift(>=3)
41 | route.append(contentsOf: presented.route())
42 | #else
43 | route.appendContentsOf(presented.route())
44 | #endif
45 | }
46 | return route
47 | }
48 |
49 | public func needsToPrepareForRoute(_ newValue: Route?) -> Bool {
50 | pruneOutOfDatePresented()
51 |
52 | guard let route = newValue else {
53 | if presented != nil {
54 | return true
55 | }
56 | return rootCoordinator.needsToPrepareForRoute(nil)
57 | }
58 |
59 | let (rootRoute, presentable) = parsePresentableRoute(route)
60 | let rootNeedsToPrepare = rootCoordinator.needsToPrepareForRoute(rootRoute)
61 |
62 | guard let presented = presented else {
63 | return rootNeedsToPrepare
64 | }
65 |
66 | guard
67 | let newPresentable = presentable,
68 | presented.tag == newPresentable.scene.tag,
69 | !newPresentable.route.components.isEmpty
70 | else {
71 | return true
72 | }
73 |
74 | let presentedNeedsToPrepare = presented.coordinator.needsToPrepareForRoute(newPresentable.route)
75 | return rootNeedsToPrepare || presentedNeedsToPrepare
76 | }
77 |
78 | public func prepareForRoute(_ newValue: Route?, completionHandler: @escaping () -> Void) {
79 | guard let route = newValue else {
80 | withGroup(completionHandler) { group in
81 | group.enter()
82 | rootCoordinator.prepareForRoute(nil) {
83 | group.leave()
84 | }
85 | group.enter()
86 | dismiss() {
87 | group.leave()
88 | }
89 | }
90 | return
91 | }
92 |
93 | withGroup(completionHandler) { group in
94 | let (rootRoute, presentable) = parsePresentableRoute(route)
95 |
96 | group.enter()
97 | rootCoordinator.prepareForRoute(rootRoute) {
98 | group.leave()
99 | }
100 |
101 | if let presented = presented {
102 | group.enter()
103 | if let presentedRoute = presentable?.route {
104 | presented.coordinator.prepareForRoute(presentedRoute) {
105 | group.leave()
106 | }
107 | } else {
108 | dismiss() {
109 | group.leave()
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
116 | public func setRoute(_ newValue: Route?, completionHandler: @escaping () -> Void) {
117 | guard let newValue = newValue else {
118 | dismiss(completionHandler: completionHandler)
119 | return
120 | }
121 |
122 | guard newValue != route else {
123 | completionHandler()
124 | return
125 | }
126 |
127 | withGroup(completionHandler) { group in
128 | let (rootRoute, presentable) = parsePresentableRoute(newValue)
129 |
130 | group.enter()
131 | rootCoordinator.setRoute(rootRoute) {
132 | group.leave()
133 | }
134 |
135 | group.enter()
136 | if let presentable = presentable {
137 | present(scene: presentable.scene, route: presentable.route) {
138 | group.leave()
139 | }
140 | } else {
141 | dismiss() {
142 | group.leave()
143 | }
144 | }
145 | }
146 | }
147 |
148 | func start(route: Route?) {
149 | guard let route = route else {
150 | rootCoordinator.start(route: nil)
151 | return
152 | }
153 |
154 | let (rootRoute, presentable) = parsePresentableRoute(route)
155 | rootCoordinator.start(route: rootRoute)
156 |
157 | if let presentable = presentable {
158 | rootCoordinator.rootViewController.corduxToPresent = ToPresentBox(route: presentable.route, scene: presentable.scene)
159 | }
160 | }
161 |
162 | /// Checks whether the presented coordinator's view controller is still presented (or nil if none).
163 | ///
164 | /// A dead presented coordinator can occur if the presented view controller has already been dismissed outside of
165 | /// normal routing, e.g. from a button press on a UIAlertController.
166 | var isPresentedViewControllerUpToDate: Bool {
167 | return rootViewController.presentedViewController == presented?.coordinator.rootViewController
168 | }
169 |
170 | /// Prunes the current presented coordinator if not up to date
171 | func pruneOutOfDatePresented() {
172 | if !isPresentedViewControllerUpToDate {
173 | self.presented = nil
174 | }
175 | }
176 |
177 | public var hasStoredPresentable: Bool {
178 | return rootCoordinator.rootViewController.corduxToPresent != nil
179 | }
180 |
181 | public func presentStoredPresentable(completionHandler: @escaping () -> Void) {
182 | guard let toPresent = rootCoordinator.rootViewController.corduxToPresent else {
183 | completionHandler()
184 | return
185 | }
186 |
187 | rootCoordinator.rootViewController.corduxToPresent = nil
188 | present(scene: toPresent.scene, route: toPresent.route, completionHandler: completionHandler)
189 | }
190 |
191 | func parsePresentableRoute(_ route: Route) -> (rootRoute: Route, presentable: (route: Route, scene: GeneratingScene)?) {
192 | for presentable in presentables {
193 | let parts = route.components.split(separator: presentable.tag,
194 | maxSplits: 2,
195 | omittingEmptySubsequences: false)
196 | if parts.count == 2 {
197 | return (Route(parts[0]), (Route(parts[1]), presentable))
198 | }
199 | }
200 | return (route, nil)
201 | }
202 |
203 | /// Presents the presentable coordinator.
204 | /// If it is already presented, this method merely adjusts the route.
205 | /// If a different presentable is currently presented, this method dismisses it first.
206 | func present(scene: GeneratingScene, route: Route, completionHandler: @escaping () -> Void) {
207 | if let presented = presented {
208 | if scene.tag != presented.tag {
209 | dismiss() {
210 | DispatchQueue.main.async {
211 | self.present(scene: scene, route: route, completionHandler: completionHandler)
212 | }
213 | }
214 | } else {
215 | presented.coordinator.setRoute(route, completionHandler: completionHandler)
216 | }
217 | return
218 | }
219 |
220 | let coordinator = scene.buildCoordinator()
221 | coordinator.start(route: route)
222 | let presentedController = coordinator.rootViewController
223 | presentedController.addLifecycleDelegate(self)
224 | rootViewController.present(presentedController, animated: true) {
225 | self.presented = Scene(tag: scene.tag, coordinator: coordinator)
226 | completionHandler()
227 | }
228 | }
229 |
230 | /// Dismisses the currently presented coordinator if present. Noop if there isn't one.
231 | func dismiss(completionHandler: @escaping () -> Void) {
232 | guard let presented = presented else {
233 | completionHandler()
234 | return
235 | }
236 |
237 | presented.coordinator.prepareForRoute(nil) {
238 | presented.coordinator.rootViewController.dismiss(animated: true) {
239 | self.presented = nil
240 | completionHandler()
241 | }
242 | }
243 | }
244 |
245 | /// Helper method for synchronizing activities with a DispatchGroup
246 | ///
247 | /// - Parameter perform: Callback to do work with the group, executed on the main queue
248 | func withGroup(_ completionHandler: @escaping () -> Void, perform: (DispatchGroup) -> Void) {
249 | let group = DispatchGroup()
250 | perform(group)
251 | let queue = DispatchQueue(label: "PresentingCoordinatorSync")
252 | queue.async {
253 | group.wait()
254 | DispatchQueue.main.async(execute: completionHandler)
255 | }
256 | }
257 | }
258 |
259 | // MARK: - Storage Helpers
260 |
261 | fileprivate final class ToPresentBox: NSObject {
262 | let route: Route
263 | let scene: GeneratingScene
264 |
265 | init(route: Route, scene: GeneratingScene) {
266 | self.route = route
267 | self.scene = scene
268 | }
269 | }
270 |
271 | extension UIViewController {
272 | private struct CorduxPresentingCoordinatorKeys {
273 | static var ToPresent = "cordux_to_present"
274 | }
275 |
276 | fileprivate var corduxToPresent: ToPresentBox? {
277 | get {
278 | return objc_getAssociatedObject(self, &CorduxPresentingCoordinatorKeys.ToPresent) as? ToPresentBox
279 | }
280 |
281 | set {
282 | if let newValue = newValue {
283 | objc_setAssociatedObject(
284 | self,
285 | &CorduxPresentingCoordinatorKeys.ToPresent,
286 | newValue as ToPresentBox?,
287 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC
288 | )
289 | }
290 | }
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/Sources/Reducer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Reducer.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 7/28/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol AnyReducer {
12 | func _handleAction(_ action: Action, state: StateType) -> StateType
13 | }
14 |
15 | public protocol Reducer: AnyReducer {
16 | associatedtype State
17 | func handleAction(_ action: Action, state: State) -> State
18 | }
19 |
20 | public extension Reducer {
21 | func _handleAction(_ action: Action, state: StateType) -> StateType {
22 | return withSpecificTypes(action, state: state, function: handleAction)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/Routing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Routing.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Route {
12 | public var components: [String]
13 |
14 | public func reduce(_ action: RouteAction) -> Route {
15 | switch action {
16 | case .goto(let route):
17 | return route.route()
18 | case .push(let segment):
19 | return route() + segment.route()
20 | case .pop(let segment):
21 | let segmentRoute = segment.route()
22 | let n = route().count
23 | let m = segmentRoute.count
24 | guard n >= m else {
25 | return self
26 | }
27 | let tail = Route(Array(route()[n-m.. Route
41 | }
42 |
43 | public enum RouteAction: Action {
44 | case goto(T)
45 | case push(T)
46 | case pop(T)
47 | case replace(T, T)
48 | }
49 |
50 | #if swift(>=3)
51 | extension Route:
52 | RandomAccessCollection,
53 | Sequence,
54 | RangeReplaceableCollection,
55 | ExpressibleByArrayLiteral
56 | {}
57 | #else
58 | extension Route:
59 | CollectionType,
60 | SequenceType,
61 | RangeReplaceableCollectionType,
62 | ArrayLiteralConvertible
63 | {}
64 | #endif
65 |
66 | extension Route {
67 | public init() {
68 | self.components = []
69 | }
70 |
71 | public init(_ components: [String]) {
72 | self.components = components
73 | }
74 |
75 | public init(_ component: String) {
76 | self.init([component])
77 | }
78 |
79 | public init(_ slice: Slice) {
80 | self.init(Array(slice))
81 | }
82 | }
83 |
84 | extension Route: Equatable {}
85 |
86 | public func ==(lhs: Route, rhs: Route) -> Bool {
87 | return lhs.components == rhs.components
88 | }
89 |
90 | extension Route: RouteConvertible {
91 | public func route() -> Route {
92 | return self
93 | }
94 | }
95 |
96 | extension Route {
97 | public init(arrayLiteral elements: String...) {
98 | components = elements
99 | }
100 | }
101 |
102 | extension Route {
103 | func isPrefixed(with route: Route) -> Bool {
104 | return Array(components.prefix(route.components.count)) == route.components
105 | }
106 | }
107 |
108 |
109 | extension Route {
110 | #if swift(>=3)
111 | public typealias Iterator = AnyIterator
112 | public func makeIterator() -> Iterator {
113 | return AnyIterator(makeGenerator())
114 | }
115 | #else
116 | public typealias Generator = AnyGenerator
117 | public func generate() -> Generator {
118 | return AnyGenerator(body: makeGenerator())
119 | }
120 | #endif
121 |
122 | func makeGenerator() -> () -> (String?) {
123 | var index = 0
124 | return {
125 | if index < self.components.count {
126 | let c = self.components[index]
127 | index += 1
128 | return c
129 |
130 | }
131 | return nil
132 | }
133 | }
134 | }
135 |
136 | extension Route {
137 | public typealias Index = Int
138 |
139 | #if swift(>=3)
140 | public typealias Indices = CountableRange
141 | #endif
142 |
143 | public var startIndex: Int {
144 | return 0
145 | }
146 |
147 | public var endIndex: Int {
148 | return components.count
149 | }
150 |
151 | public func index(after i: Int) -> Int {
152 | return i + 1
153 | }
154 |
155 | public func index(before i: Int) -> Int {
156 | return i - 1
157 | }
158 |
159 | public subscript(i: Int) -> String {
160 | return components[i]
161 | }
162 | }
163 |
164 | extension Route {
165 | public mutating func reserveCapacity(_ minimumCapacity: Int) {
166 | components.reserveCapacity(minimumCapacity)
167 | }
168 |
169 | #if swift(>=3)
170 | public mutating func replaceSubrange(_ subRange: Range, with newElements: C) where C.Iterator.Element == Iterator.Element {
171 | components.replaceSubrange(subRange, with: newElements)
172 | }
173 | #else
174 | public mutating func replaceRange(_ subRange: Range, with newElements: C) where C.Generator.Element == Generator.Element {
175 | components.replaceRange(subRange, with: newElements)
176 | }
177 | #endif
178 | }
179 |
180 | public func +(lhs: Route, rhs: Route) -> Route {
181 | return Route(lhs.components + rhs.components)
182 | }
183 |
184 | public func +(lhs: RouteConvertible, rhs: Route) -> Route {
185 | return Route(lhs.route().components + rhs.components)
186 | }
187 |
188 | public func +(lhs: Route, rhs: RouteConvertible) -> Route {
189 | return Route(lhs.components + rhs.route().components)
190 | }
191 |
192 | extension String: RouteConvertible {
193 | public func route() -> Route {
194 | return Route(self)
195 | }
196 | }
197 |
198 | public extension RawRepresentable where RawValue == String {
199 | public func route() -> Route {
200 | return self.rawValue.route()
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/Sources/SceneCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneCoordinator.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 7/28/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// A SceneCoordinator can manage the transition between disparate coordinators.
12 | /// For instance, if the coordinator has three child scenes, A, B, and C, then
13 | /// the scene coordinator can manage switching between them, showing only one at a time,
14 | /// as well as routing the subroute to the current scene (either A, B, or C).
15 | ///
16 | /// To do this, it wants a route prefix to indicate which scene should be shown, and
17 | /// a currentScene property to know which scene to route to.
18 | public protocol SceneCoordinator: Coordinator {
19 | /// The current scene being shown by the coordinator.
20 | var currentScene: Scene? { get set }
21 |
22 | func coordinatorForTag(_ tag: String) -> (coordinator: AnyCoordinator, started: Bool)?
23 | func presentCoordinator(_ coordinator: AnyCoordinator?, completionHandler: @escaping () -> Void)
24 | }
25 |
26 | public extension SceneCoordinator {
27 | public var route: Route {
28 | return currentScene?.route() ?? []
29 | }
30 |
31 | public func needsToPrepareForRoute(_ route: Route?) -> Bool {
32 | guard let currentScene = currentScene else {
33 | return false
34 | }
35 |
36 | guard let route = route else {
37 | return currentScene.coordinator.needsToPrepareForRoute(nil)
38 | }
39 |
40 | return currentScene.coordinator.needsToPrepareForRoute(route)
41 | }
42 |
43 | public func prepareForRoute(_ route: Route?, completionHandler: @escaping () -> Void) {
44 | guard let currentScene = currentScene else {
45 | completionHandler()
46 | return
47 | }
48 |
49 | guard let route = route else {
50 | currentScene.coordinator.prepareForRoute(nil, completionHandler: completionHandler)
51 | return
52 | }
53 |
54 | currentScene.coordinator.prepareForRoute(sceneRoute(route), completionHandler: completionHandler)
55 | }
56 |
57 | public func setRoute(_ route: Route?, completionHandler: @escaping () -> Void) {
58 | guard let route = route, let tag = route.first else {
59 | completionHandler()
60 | return
61 | }
62 |
63 | if tag != currentScene?.tag {
64 | if let (coordinator, started) = coordinatorForTag(tag) {
65 | let wrappedCompletionHandler = wrapPresentingCompletionHandler(coordinator: coordinator,
66 | completionHandler: completionHandler)
67 | currentScene = Scene(tag: tag, coordinator: coordinator)
68 | if !started {
69 | coordinator.start(route: sceneRoute(route))
70 | }
71 | presentCoordinator(coordinator, completionHandler: {
72 | if started {
73 | coordinator.setRoute(self.sceneRoute(route), completionHandler: wrappedCompletionHandler)
74 | } else {
75 | wrappedCompletionHandler()
76 | }
77 | })
78 | } else {
79 | presentCoordinator(nil, completionHandler: completionHandler)
80 | }
81 | } else {
82 | currentScene?.coordinator.setRoute(sceneRoute(route), completionHandler: completionHandler)
83 | }
84 | }
85 |
86 | func sceneRoute(_ route: Route) -> Route {
87 | return Route(route.dropFirst())
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Sources/Scenes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TaggedCoordinator.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 11/2/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class Scene: RouteConvertible {
12 | /// The label used to identify the coordinator and to be used as a route segment.
13 | public let tag: String
14 |
15 | /// The coordinator to attach with the label.
16 | public let coordinator: AnyCoordinator
17 |
18 | /// Initializes a Scene.
19 | ///
20 | /// - Parameters:
21 | /// - tag: The label used to identify the coordinator and to be used as a route segment.
22 | /// - coordinator: The coordinator to attach with the label.
23 | public init(tag: String, coordinator: AnyCoordinator) {
24 | self.tag = tag
25 | self.coordinator = coordinator
26 | }
27 |
28 | public func route() -> Route {
29 | return Route(tag) + coordinator.route
30 | }
31 | }
32 |
33 | public class GeneratingScene {
34 | /// The label used to identify the coordinator and to be used as a route segment.
35 | public let tag: String
36 |
37 | let factory: ()->(AnyCoordinator)
38 |
39 | /// Initializes a GeneratingScene.
40 | ///
41 | /// - Parameters:
42 | /// - tag: The label used to identify the coordinator and to be used as a route segment.
43 | /// - factory: A closure to create the coordinator for the scene.
44 | ///
45 | /// - Important: The factory closure is stored indefinitely. If you are storing this type for later use, be sure
46 | /// to mind what you capture over. Most often this will mean you should be using [unowned self].
47 | public init(tag: String, factory: @escaping ()->(AnyCoordinator)) {
48 | self.tag = tag
49 | self.factory = factory
50 | }
51 |
52 | /// Builds a new coordinator from the factory provided at initialization.
53 | public func buildCoordinator() -> AnyCoordinator {
54 | return self.factory()
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/SimpleCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleCoordinator.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 11/7/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SimpleCoordinator: Coordinator {
12 |
13 | }
14 |
15 | public extension SimpleCoordinator {
16 | public func needsToPrepareForRoute(_ route: Route?) -> Bool {
17 | return false
18 | }
19 |
20 | public func prepareForRoute(_ route: Route?, completionHandler: @escaping () -> Void) {
21 | completionHandler()
22 | }
23 |
24 | public func setRoute(_ route: Route?, completionHandler: @escaping () -> Void) {
25 | completionHandler()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Store.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Store.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 7/28/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | private let kRouteTimeoutDuration: TimeInterval = 3
12 |
13 | /// Action is a marker type that describes types that can modify state.
14 | public protocol Action {}
15 |
16 | /// StateType describes the minimum requirements for state.
17 | public protocol StateType {
18 |
19 | /// The current representation of the route for the app.
20 | ///
21 | /// This describes what the user is currently seeing and how they navigated there.
22 | var route: Route { get set }
23 | }
24 |
25 | public final class Store {
26 | public private(set) var state: State
27 | public let reducer: AnyReducer
28 | public let middlewares: [AnyMiddleware]
29 |
30 | /// Isolation queue to protect routingCount
31 | private let routeStatusQueue = DispatchQueue(label: "CorduxRoutingStatus")
32 | /// The number of routing actions currently in progress
33 | private var routingCount = 0
34 | /// Serial queue to order routing actions
35 | private let routeQueue = DispatchQueue(label: "CorduxRouting", qos: .userInitiated)
36 | /// Logger to log routing events
37 | private let routeLogger: RouteLogger?
38 | /// The last route that has been propagated
39 | private var lastRoute: Route?
40 |
41 | /// The root coordinator for the app; used for routing.
42 | public weak var rootCoordinator: AnyCoordinator? {
43 | didSet {
44 | propagateRoute(state.route)
45 | }
46 | }
47 |
48 | typealias SubscriptionType = Subscription
49 | var subscriptions: [SubscriptionType] = []
50 |
51 | public init(initialState: State, reducer: AnyReducer, middlewares: [AnyMiddleware] = [],
52 | routeLogger: RouteLogger? = ConsoleRouteLogger) {
53 | self.state = initialState
54 | self.reducer = reducer
55 | self.middlewares = middlewares
56 | self.routeLogger = routeLogger
57 | }
58 |
59 | #if swift(>=3)
60 | public func subscribe(_ subscriber: Subscriber, _ transform: ((State) -> SelectedState)? = nil) where Subscriber.StoreSubscriberStateType == SelectedState {
61 | guard isNewSubscriber(subscriber) else {
62 | return
63 | }
64 |
65 | let sub = Subscription(subscriber: subscriber, transform: transform)
66 | subscriptions.append(sub)
67 | sub.subscriber?._newState(sub.transform?(state) ?? state)
68 | }
69 | #else
70 | public func subscribe(_ subscriber: Subscriber, _ transform: ((State) -> SelectedState)? = nil) {
71 | guard isNewSubscriber(subscriber) else {
72 | return
73 | }
74 |
75 | let sub = Subscription(subscriber: subscriber, transform: transform)
76 | subscriptions.append(sub)
77 | sub.subscriber?._newState(sub.transform?(state) ?? state)
78 | }
79 | #endif
80 |
81 | public func unsubscribe(_ subscriber: AnyStoreSubscriber) {
82 | #if swift(>=3)
83 | if let index = subscriptions.index(where: { return $0.subscriber === subscriber }) {
84 | subscriptions.remove(at: index)
85 | }
86 | #else
87 | if let index = subscriptions.index(where: { return $0.subscriber === subscriber }) {
88 | subscriptions.remove(at: index)
89 | }
90 | #endif
91 | }
92 |
93 | public func route(_ action: RouteAction) {
94 | state.route = state.route.reduce(action)
95 | routeLogger?(.store(state.route))
96 | dispatch(action)
97 | }
98 |
99 | public func setRoute(_ action: RouteAction) {
100 | state.route = state.route.reduce(action)
101 | lastRoute = state.route
102 | routeLogger?(.set(state.route))
103 | }
104 |
105 | public func dispatch(_ action: Action) {
106 | let state = self.state
107 | middlewares.forEach { $0._before(action: action, state: state) }
108 | let newState = reducer._handleAction(action, state: state) as! State
109 | self.state = newState
110 |
111 | #if swift(>=3)
112 | middlewares.reversed().forEach { $0._after(action: action, state: newState) }
113 | #else
114 | middlewares.reverse().forEach { $0._after(action: action, state: newState) }
115 | #endif
116 |
117 | if state.route != newState.route {
118 | routeLogger?(.reducer(newState.route))
119 | }
120 |
121 | propagateRoute(newState.route)
122 |
123 | subscriptions = subscriptions.filter { $0.subscriber != nil }
124 | subscriptions.forEach { $0.subscriber?._newState($0.transform?(newState) ?? newState) }
125 | }
126 |
127 | /// Propagates the route through the app via the `rootCoordinator`.
128 | ///
129 | /// This method will execute the routing action synchronously if no other routing actions are already in progress.
130 | /// If a routing action is already occurring, the route will be performed asynchronously. All actions occur on
131 | /// the main thread.
132 | ///
133 | /// - Note: Does not re-propagate identical routes
134 | /// - Note: If a route is attempted to be propagated while another route is working, it is executed after the
135 | /// original propagation completes. Multiple routes are propagated asynchronously in order.
136 | /// - Note: All routing methods must call their completion handlers on the main thread.
137 | /// This method times out after 3 seconds.
138 | ///
139 | /// - Parameter route: The route to propagate
140 | private func propagateRoute(_ route: Route) {
141 | guard route != lastRoute, let rootCoordinator = rootCoordinator else {
142 | return
143 | }
144 | lastRoute = route
145 |
146 | func waitForRoutingCompletion(_ group: DispatchGroup) {
147 | let result = group.wait(timeout: .now() + kRouteTimeoutDuration)
148 | if case .timedOut = result {
149 | alertStuckRouter()
150 | }
151 | self.decrementRoutingCount()
152 | }
153 |
154 | func _setRoute(_ group: DispatchGroup) {
155 | rootCoordinator.setRoute(route) {
156 | group.leave()
157 | }
158 | }
159 |
160 | func setRoute(_ group: DispatchGroup) {
161 | guard rootCoordinator.needsToPrepareForRoute(route) else {
162 | _setRoute(group)
163 | return
164 | }
165 | rootCoordinator.prepareForRoute(route) {
166 | _setRoute(group)
167 | }
168 | }
169 |
170 | let shouldRouteAsynchronously = isRouting
171 | self.incrementRoutingCount()
172 |
173 | if shouldRouteAsynchronously {
174 | routeQueue.async {
175 | let group = DispatchGroup()
176 | group.enter()
177 | DispatchQueue.main.async {
178 | setRoute(group)
179 | }
180 | waitForRoutingCompletion(group)
181 | }
182 | } else {
183 | let group = DispatchGroup()
184 | group.enter()
185 | routeQueue.async {
186 | waitForRoutingCompletion(group)
187 | }
188 | setRoute(group)
189 | }
190 | }
191 |
192 | private var isRouting: Bool { return routeStatusQueue.sync { self.routingCount > 0 } }
193 | private func incrementRoutingCount() { routeStatusQueue.async { self.routingCount += 1 } }
194 | private func decrementRoutingCount() { routeStatusQueue.async { self.routingCount -= 1 } }
195 |
196 | func isNewSubscriber(_ subscriber: AnyStoreSubscriber) -> Bool {
197 | #if swift(>=3)
198 | guard !subscriptions.contains(where: { $0.subscriber === subscriber }) else {
199 | return false
200 | }
201 | #else
202 | guard !subscriptions.contains({ $0.subscriber === subscriber }) else {
203 | return false
204 | }
205 | #endif
206 | return true
207 | }
208 | }
209 |
210 | func alertStuckRouter() {
211 | print("[Cordux]: Router is stuck waiting for a completion handler to be called.")
212 | print("[Cordux]: Please make sure that you have called the completion handler in all routing methods (prepareForRoute, setRoute, updateRoute).")
213 | print("[Cordux]: Set a symbolic breakpoint for the `CorduxRouterStuck` symbol in order to halt the program when this happens.")
214 | CorduxRouterStuck()
215 | }
216 |
217 | func CorduxRouterStuck() {}
218 |
--------------------------------------------------------------------------------
/Sources/Subscriptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Cordux.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/21/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Subscription {
12 | weak var subscriber: AnyStoreSubscriber?
13 | let transform: ((State) -> Any)?
14 | }
15 |
16 | public protocol AnyStoreSubscriber: class {
17 | func _newState(_ state: Any)
18 | }
19 |
20 | public protocol SubscriberType: AnyStoreSubscriber {
21 | associatedtype StoreSubscriberStateType
22 | func newState(_ subscription: StoreSubscriberStateType)
23 | }
24 |
25 | extension SubscriberType {
26 | public func _newState(_ state: Any) {
27 | if let typedState = state as? StoreSubscriberStateType {
28 | newState(typedState)
29 | } else {
30 | #if swift(>=3)
31 | preconditionFailure("Expected \(StoreSubscriberStateType.self) but received \(type(of:state))")
32 | #else
33 | preconditionFailure("Expected \(StoreSubscriberStateType.self) but received \(state.dynamicType))")
34 | #endif
35 | }
36 | }
37 | }
38 |
39 | /// Renderer is a special subscriber that has semantics that match what we expect
40 | /// in a view controller.
41 | public protocol Renderer: AnyStoreSubscriber {
42 | associatedtype ViewModel
43 | func render(_ viewModel: ViewModel)
44 | }
45 |
46 | extension Renderer {
47 | public func _newState(_ state: Any) {
48 | if let viewModel = state as? ViewModel {
49 | render(viewModel)
50 | } else {
51 | #if swift(>=3)
52 | preconditionFailure("Expected \(ViewModel.self) but received \(type(of:state))")
53 | #else
54 | preconditionFailure("Expected \(ViewModel.self) but received \(state.dynamicType))")
55 | #endif
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/TabBarControllerCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarControllerCoordinator.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 7/28/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// A scene coordinator that keeps its scenes in an array (always in memory), and is rendered by a UITabBarController.
12 | /// Conforming types' start method should invoke the start method for each scene.
13 | public protocol TabBarControllerCoordinator: SceneCoordinator {
14 | var tabBarController: UITabBarController { get }
15 | var scenes: [Scene] { get }
16 | }
17 |
18 | public extension TabBarControllerCoordinator {
19 | public var rootViewController: UIViewController { return tabBarController }
20 | public var currentScene: Scene? {
21 | get {
22 | return scenes[tabBarController.selectedIndex]
23 | }
24 | set {
25 | // noop
26 | // The coordinator is retained by scenes array and identified by tabBarController.selectedIndex.
27 | }
28 | }
29 |
30 | func coordinatorForTag(_ tag: String) -> (coordinator: AnyCoordinator, started: Bool)? {
31 | return scenes.first(where: { $0.tag == tag }).map { ($0.coordinator, true) }
32 | }
33 |
34 | func presentCoordinator(_ coordinator: AnyCoordinator?, completionHandler: @escaping () -> Void) {
35 | guard let coordinator = coordinator else {
36 | completionHandler()
37 | return
38 | }
39 |
40 | tabBarController.selectedIndex = scenes.index(where: { $0.coordinator === coordinator }) ?? 0
41 | completionHandler()
42 | }
43 |
44 | public func setRouteForViewController(_ viewController: UIViewController) -> Bool {
45 | for scene in scenes {
46 | if scene.coordinator.rootViewController === viewController {
47 | store.setRoute(.replace(route, scene.route()))
48 | return true
49 | }
50 | }
51 | return false
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/TypeHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TypeHelpers.swift
3 | // Cordux
4 | //
5 | // Created by Ian Terrell on 8/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | #if swift(>=3)
12 | func withSpecificTypes(_ action: Action, state genericStateType: StateType,
13 | function: (_ action: Action, _ state: SpecificStateType) -> SpecificStateType) -> StateType {
14 | guard let specificStateType = genericStateType as? SpecificStateType else {
15 | return genericStateType
16 | }
17 |
18 | return function(action, specificStateType) as! StateType
19 | }
20 |
21 | func withSpecificTypes(_ action: Action, state genericStateType: StateType,
22 | function: (_ action: Action, _ state: SpecificStateType) -> ()) {
23 | guard let specificStateType = genericStateType as? SpecificStateType else {
24 | return
25 | }
26 |
27 | function(action, specificStateType)
28 | }
29 |
30 | #else
31 |
32 | func withSpecificTypes(_ action: Action, state genericStateType: StateType, @noescape function: (action: Action, state: SpecificStateType) -> SpecificStateType) -> StateType {
33 | guard let specificStateType = genericStateType as? SpecificStateType else {
34 | return genericStateType
35 | }
36 |
37 | return function(action: action, state: specificStateType) as! StateType
38 | }
39 |
40 | func withSpecificTypes(_ action: Action, state genericStateType: StateType, @noescape function: (action: Action, state: SpecificStateType) -> ()) {
41 | guard let specificStateType = genericStateType as? SpecificStateType else {
42 | return
43 | }
44 |
45 | function(action: action, state: specificStateType)
46 | }
47 | #endif
48 |
--------------------------------------------------------------------------------
/Sources/ViewControllerLifecycle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewControllerLifecycle.swift
3 | // CorduxPrototype
4 | //
5 | // Created by Ian Terrell on 7/22/16.
6 | // Copyright © 2016 WillowTree. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @objc public protocol ViewControllerLifecycleDelegate {
12 | @objc optional func viewDidLoad(viewController: UIViewController)
13 | @objc optional func viewWillAppear(_ animated: Bool, viewController: UIViewController)
14 | @objc optional func viewDidAppear(_ animated: Bool, viewController: UIViewController)
15 | @objc optional func viewWillDisappear(_ animated: Bool, viewController: UIViewController)
16 | @objc optional func viewDidDisappear(_ animated: Bool, viewController: UIViewController)
17 | @objc optional func didMove(toParentViewController: UIViewController?, viewController: UIViewController)
18 | }
19 |
20 | final class LifecycleDelegateCollection {
21 | var delegates: [_wrapper] = []
22 |
23 | func append(_ delegate: ViewControllerLifecycleDelegate?) {
24 | guard let delegate = delegate else {
25 | return
26 | }
27 | delegates.append(_wrapper(delegate))
28 | }
29 |
30 | struct _wrapper {
31 | weak var delegate: ViewControllerLifecycleDelegate?
32 | init(_ delegate: ViewControllerLifecycleDelegate) {
33 | self.delegate = delegate
34 | }
35 | }
36 | }
37 |
38 | extension LifecycleDelegateCollection {
39 | func forEach(_ body: (ViewControllerLifecycleDelegate?) -> Void) {
40 | delegates.forEach { body($0.delegate) }
41 | }
42 |
43 | func viewDidLoad(viewController: UIViewController) {
44 | forEach { $0?.viewDidLoad?(viewController: viewController) }
45 | }
46 |
47 | func viewWillAppear(_ animated: Bool, viewController: UIViewController) {
48 | forEach { $0?.viewWillAppear?(animated, viewController: viewController) }
49 | }
50 |
51 | func viewDidAppear(_ animated: Bool, viewController: UIViewController) {
52 | forEach { $0?.viewDidAppear?(animated, viewController: viewController) }
53 | }
54 |
55 | func viewWillDisappear(_ animated: Bool, viewController: UIViewController) {
56 | forEach { $0?.viewWillDisappear?(animated, viewController: viewController) }
57 | }
58 |
59 | func viewDidDisappear(_ animated: Bool, viewController: UIViewController) {
60 | forEach { $0?.viewDidDisappear?(animated, viewController: viewController) }
61 | }
62 |
63 | func didMove(toParentViewController parent: UIViewController?, viewController: UIViewController) {
64 | forEach { $0?.didMove?(toParentViewController: parent, viewController: viewController) }
65 | }
66 | }
67 |
68 | extension UIViewController {
69 | static let swizzle: Void = {
70 | UIViewController.cordux_swizzleMethod(#selector(UIViewController.viewDidLoad),
71 | swizzled: #selector(UIViewController.cordux_viewDidLoad))
72 |
73 | UIViewController.cordux_swizzleMethod(#selector(UIViewController.viewWillAppear),
74 | swizzled: #selector(UIViewController.cordux_viewWillAppear))
75 | UIViewController.cordux_swizzleMethod(#selector(UIViewController.viewDidAppear),
76 | swizzled: #selector(UIViewController.cordux_viewDidAppear))
77 | UIViewController.cordux_swizzleMethod(#selector(UIViewController.viewWillDisappear),
78 | swizzled: #selector(UIViewController.cordux_viewWillDisappear))
79 | UIViewController.cordux_swizzleMethod(#selector(UIViewController.viewDidDisappear),
80 | swizzled: #selector(UIViewController.cordux_viewDidDisappear))
81 |
82 | #if swift(>=3)
83 | UIViewController.cordux_swizzleMethod(#selector(UIViewController.didMove),
84 | swizzled: #selector(UIViewController.cordux_didMoveToParentViewController(_:)))
85 | #else
86 | UIViewController.cordux_swizzleMethod(#selector(UIViewController.didMoveToParentViewController(_:)),
87 | swizzled: #selector(UIViewController.cordux_didMoveToParentViewController(_:)))
88 | #endif
89 | }()
90 |
91 | public class func swizzleLifecycleDelegatingViewControllerMethods() {
92 | _ = swizzle
93 | }
94 |
95 | @objc func cordux_viewDidLoad() {
96 | self.cordux_viewDidLoad()
97 | #if swift(>=3)
98 | self.corduxContext?.lifecycleDelegate.viewDidLoad(viewController: self)
99 | #else
100 | self.corduxContext?.lifecycleDelegate.viewDidLoad(self)
101 | #endif
102 | }
103 |
104 | @objc func cordux_viewWillAppear(_ animated: Bool) {
105 | self.cordux_viewWillAppear(animated)
106 | self.corduxContext?.lifecycleDelegate.viewWillAppear(animated, viewController: self)
107 | }
108 |
109 | @objc func cordux_viewDidAppear(_ animated: Bool) {
110 | self.cordux_viewDidAppear(animated)
111 | self.corduxContext?.lifecycleDelegate.viewDidAppear(animated, viewController: self)
112 | }
113 |
114 | @objc func cordux_viewWillDisappear(_ animated: Bool) {
115 | self.cordux_viewWillDisappear(animated)
116 | self.corduxContext?.lifecycleDelegate.viewWillDisappear(animated, viewController: self)
117 | }
118 |
119 | @objc func cordux_viewDidDisappear(_ animated: Bool) {
120 | self.cordux_viewDidDisappear(animated)
121 | self.corduxContext?.lifecycleDelegate.viewDidDisappear(animated, viewController: self)
122 | }
123 |
124 | @objc func cordux_didMoveToParentViewController(_ parentViewController: UIViewController?) {
125 | self.cordux_didMoveToParentViewController(parentViewController)
126 |
127 | #if swift(>=3)
128 | self.corduxContext?.lifecycleDelegate.didMove(toParentViewController: parentViewController, viewController: self)
129 | #else
130 | self.corduxContext?.lifecycleDelegate.didMove(parentViewController, viewController: self)
131 | #endif
132 | }
133 |
134 | static func cordux_swizzleMethod(_ original: Selector, swizzled: Selector) {
135 | guard let originalMethod = class_getInstanceMethod(self, original),
136 | let swizzledMethod = class_getInstanceMethod(self, swizzled) else {
137 | return
138 | }
139 |
140 | let didAddMethod = class_addMethod(self, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
141 |
142 | if didAddMethod {
143 | class_replaceMethod(self, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
144 | } else {
145 | method_exchangeImplementations(originalMethod, swizzledMethod)
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/diagrams/Makefile:
--------------------------------------------------------------------------------
1 | DOTS=$(wildcard *.dot)
2 |
3 | .SUFFIXES: .dot .png
4 |
5 | .PHONY: all
6 | all: $(patsubst %.dot, %.png, $(DOTS))
7 |
8 | .dot.png: $(DOTS)
9 | dot -Tpng $< > $@
10 |
11 | .PHONY: clean
12 | clean:
13 | rm -f *.png
14 |
--------------------------------------------------------------------------------
/diagrams/architecture.dot:
--------------------------------------------------------------------------------
1 | digraph {
2 | label="Cordux Flow"
3 | compound=true
4 | outputorder=edgesfirst
5 |
6 | graph [fontsize=10 fontname="Helvetica-Bold"]
7 | node [shape=rect style="rounded" fontsize=12 fontname="Helvetica-Light"]
8 | edge [fontsize=10 fontname="Helvetica"]
9 |
10 |
11 | subgraph cluster_app {
12 | label="App"
13 |
14 | store
15 | }
16 |
17 | subgraph cluster_scene {
18 | label="Scene"
19 |
20 | subgraph cluster_coordinator {
21 | label="Coordinator"
22 | coordinator [label="Coordinator"]
23 | view_handler [label="View Handler"]
24 | }
25 |
26 | subgraph cluster_view {
27 | label="View"
28 |
29 | view_store_query [label="Query"]
30 | view_controller [label="View Controller"]
31 | view [label="View"]
32 | }
33 | }
34 |
35 |
36 | subgraph cluster_services {
37 | label="Services"
38 |
39 | api
40 | db
41 | }
42 |
43 | subgraph data_flow {
44 | edge [color="blue"]
45 | store -> view_store_query
46 | view_store_query -> view_controller
47 | view_controller -> view_handler
48 | coordinator -> store [ltail=cluster_coordinator]
49 | }
50 |
51 | subgraph relationships {
52 | edge [style="dotted"]
53 |
54 | coordinator -> view_handler [label="conforms to"]
55 | coordinator -> view_store_query [label="instantiates"]
56 | coordinator -> view_controller [label="instantiates"]
57 |
58 |
59 | view_controller -> view [label="controls"]
60 | view -> view_controller
61 | }
62 |
63 | coordinator -> db [weight=10;lhead=cluster_services]
64 | db -> coordinator [ltail=cluster_services]
65 |
66 | subgraph layout {
67 | edge [style="invis"]
68 |
69 | store -> view_store_query
70 | coordinator -> view_controller [weight=10]
71 | coordinator -> view_handler [weight=100]
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/yougot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willowtreeapps/cordux/7b9ac675e008f0e4a6e7b404dba5aebe363e0c48/yougot.jpg
--------------------------------------------------------------------------------