├── .gitignore
├── LICENSE
├── README.md
├── XCoordinator-Example.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcshareddata
│ └── xcschemes
│ └── XCoordinator-Example.xcscheme
├── XCoordinator-Example
├── Animations
│ ├── Animation+Fade.swift
│ ├── Animation+Modal.swift
│ ├── Animation+Navigation.swift
│ ├── Animation+Scale.swift
│ └── Animation+Swirl.swift
├── Common
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ └── LaunchScreen.xib
│ └── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-20.png
│ │ ├── Icon-20@2x-1.png
│ │ ├── Icon-20@2x.png
│ │ ├── Icon-20@3x.png
│ │ ├── Icon-29.png
│ │ ├── Icon-29@2x-1.png
│ │ ├── Icon-29@2x.png
│ │ ├── Icon-29@3x.png
│ │ ├── Icon-40.png
│ │ ├── Icon-40@2x-1.png
│ │ ├── Icon-40@2x.png
│ │ ├── Icon-40@3x.png
│ │ ├── Icon-60@2x.png
│ │ ├── Icon-60@3x.png
│ │ ├── Icon-76.png
│ │ ├── Icon-76@2x.png
│ │ ├── Icon-83.5@2x.png
│ │ └── iTunesArtwork@2x.png
│ │ ├── AppLogo.imageset
│ │ ├── Contents.json
│ │ └── iTunesArtwork@2x.png
│ │ └── Contents.json
├── Coordinators
│ ├── AboutCoordinator.swift
│ ├── AppCoordinator.swift
│ ├── HomePageCoordinator.swift
│ ├── HomeSplitCoordinator.swift
│ ├── HomeTabCoordinator.swift
│ ├── NewsCoordinator.swift
│ ├── UserCoordinator.swift
│ └── UserListCoordinator.swift
├── Extensions
│ ├── CGAffineTransform+InPlace.swift
│ ├── Presentable+Rx.swift
│ ├── TransitionAnimation+Defaults.swift
│ └── Transitions.swift
├── Info.plist
├── Models
│ ├── News.swift
│ └── User.swift
├── Scenes
│ ├── About
│ │ ├── AboutViewController.swift
│ │ ├── AboutViewModel.swift
│ │ └── AboutViewModelImpl.swift
│ ├── Home
│ │ ├── HomeViewController.swift
│ │ ├── HomeViewController.xib
│ │ ├── HomeViewModel.swift
│ │ └── HomeViewModelImpl.swift
│ ├── Login
│ │ ├── LoginViewController.swift
│ │ ├── LoginViewController.xib
│ │ ├── LoginViewModel.swift
│ │ └── LoginViewModelImpl.swift
│ ├── News
│ │ ├── NewsViewController.swift
│ │ ├── NewsViewController.xib
│ │ ├── NewsViewModel.swift
│ │ └── NewsViewModelImpl.swift
│ ├── NewsDetail
│ │ ├── NewsDetailViewController.swift
│ │ ├── NewsDetailViewController.xib
│ │ ├── NewsDetailViewModel.swift
│ │ └── NewsDetailViewModelImpl.swift
│ ├── User
│ │ ├── UserViewController.swift
│ │ ├── UserViewController.xib
│ │ ├── UserViewModel.swift
│ │ └── UserViewModelImpl.swift
│ └── UserList
│ │ ├── UsersViewController.swift
│ │ ├── UsersViewController.xib
│ │ ├── UsersViewModel.swift
│ │ └── UsersViewModelImpl.swift
├── Services
│ ├── NewsService.swift
│ └── UserService.swift
├── Utils
│ ├── BindableType.swift
│ └── NibIdentifiable.swift
└── Views
│ └── DetailTableViewCell.swift
├── XCoordinator-ExampleTests
├── AnimationTests.swift
├── Info.plist
├── TestAnimation.swift
├── TestRoute.swift
├── TransitionTests.swift
├── XCTestManifests.swift
├── XCText+Extras.swift
└── XCoordinator-Example.xctestplan
└── XCoordinator-ExampleUITests
├── Info.plist
└── XCoordinator_ExampleUITests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 QuickBird Studios
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 |
2 |
3 |
4 |
5 |
6 | # XCoordinator-Example
7 |
8 | XCoordinator-Example is a MVVM-C example app for [XCoordinator](https://github.com/quickbirdstudios/XCoordinator). For a MVC example app, have a look at [a workshop](https://github.com/quickbirdstudios/Mobile-HackNight-XCoordinator) we did with a previous version of XCoordinator.
9 |
10 | ## 👤 Author
11 | This example app is created with ❤️ by [QuickBird Studios](https://quickbirdstudios.com).
12 |
13 | ## ❤️ Contributing
14 |
15 | Open an issue if you need help, if you found a bug, or if you want to discuss a feature request. If you feel like having a chat about XCoordinator or this example app with the developers and other users, join our [Slack Workspace](https://join.slack.com/t/xcoordinator/shared_invite/enQtNDg4NDAxNTk1ODQ1LTRhMjY0OTAwNWMyYmQ5ZWI5Mzk3ODU1NGJmMWZlZDY3Y2Q0NTZjOWNkMjgyNmQwYjY4MzZmYTRhN2EzMzczNTM).
16 |
17 | Open a PR if you want to make changes to the XCoordinator example app.
18 |
19 | ## 📃 License
20 |
21 | XCoordinator-Example is released under an MIT license. See [License.md](https://github.com/quickbirdstudios/XCoordinator-Example/blob/master/LICENSE) for more information.
22 |
--------------------------------------------------------------------------------
/XCoordinator-Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 9B0A753A2316C1810092CA3A /* XCoordinatorRx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B0A75392316C1810092CA3A /* XCoordinatorRx */; };
11 | 9B56575B2315FA7E00F4F4F7 /* XCoordinator_ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56575A2315FA7E00F4F4F7 /* XCoordinator_ExampleUITests.swift */; };
12 | 9B5657A72315FB3500F4F4F7 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657692315FB3500F4F4F7 /* AppCoordinator.swift */; };
13 | 9B5657A82315FB3500F4F4F7 /* UserListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56576A2315FB3500F4F4F7 /* UserListCoordinator.swift */; };
14 | 9B5657A92315FB3500F4F4F7 /* HomeSplitCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56576B2315FB3500F4F4F7 /* HomeSplitCoordinator.swift */; };
15 | 9B5657AA2315FB3500F4F4F7 /* UserCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56576C2315FB3500F4F4F7 /* UserCoordinator.swift */; };
16 | 9B5657AB2315FB3500F4F4F7 /* NewsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56576D2315FB3500F4F4F7 /* NewsCoordinator.swift */; };
17 | 9B5657AC2315FB3500F4F4F7 /* HomeTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56576E2315FB3500F4F4F7 /* HomeTabCoordinator.swift */; };
18 | 9B5657AD2315FB3500F4F4F7 /* HomePageCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56576F2315FB3500F4F4F7 /* HomePageCoordinator.swift */; };
19 | 9B5657AE2315FB3500F4F4F7 /* AboutCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657702315FB3500F4F4F7 /* AboutCoordinator.swift */; };
20 | 9B5657AF2315FB3500F4F4F7 /* NewsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657722315FB3500F4F4F7 /* NewsService.swift */; };
21 | 9B5657B02315FB3500F4F4F7 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B5657742315FB3500F4F4F7 /* LaunchScreen.xib */; };
22 | 9B5657B12315FB3500F4F4F7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B5657762315FB3500F4F4F7 /* Images.xcassets */; };
23 | 9B5657B22315FB3500F4F4F7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657772315FB3500F4F4F7 /* AppDelegate.swift */; };
24 | 9B5657B32315FB3500F4F4F7 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56577A2315FB3500F4F4F7 /* HomeViewModel.swift */; };
25 | 9B5657B42315FB3500F4F4F7 /* HomeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B56577B2315FB3500F4F4F7 /* HomeViewController.xib */; };
26 | 9B5657B52315FB3500F4F4F7 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56577C2315FB3500F4F4F7 /* HomeViewController.swift */; };
27 | 9B5657B62315FB3500F4F4F7 /* HomeViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56577D2315FB3500F4F4F7 /* HomeViewModelImpl.swift */; };
28 | 9B5657B72315FB3500F4F4F7 /* NewsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56577F2315FB3500F4F4F7 /* NewsDetailViewController.swift */; };
29 | 9B5657B82315FB3500F4F4F7 /* NewsDetailViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657802315FB3500F4F4F7 /* NewsDetailViewModelImpl.swift */; };
30 | 9B5657B92315FB3500F4F4F7 /* NewsDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B5657812315FB3500F4F4F7 /* NewsDetailViewController.xib */; };
31 | 9B5657BA2315FB3500F4F4F7 /* NewsDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657822315FB3500F4F4F7 /* NewsDetailViewModel.swift */; };
32 | 9B5657BB2315FB3500F4F4F7 /* UserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657842315FB3500F4F4F7 /* UserViewModel.swift */; };
33 | 9B5657BC2315FB3500F4F4F7 /* UserViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B5657852315FB3500F4F4F7 /* UserViewController.xib */; };
34 | 9B5657BD2315FB3500F4F4F7 /* UserViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657862315FB3500F4F4F7 /* UserViewModelImpl.swift */; };
35 | 9B5657BE2315FB3500F4F4F7 /* UserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657872315FB3500F4F4F7 /* UserViewController.swift */; };
36 | 9B5657BF2315FB3500F4F4F7 /* UsersViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657892315FB3500F4F4F7 /* UsersViewModelImpl.swift */; };
37 | 9B5657C02315FB3500F4F4F7 /* UsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56578A2315FB3500F4F4F7 /* UsersViewController.swift */; };
38 | 9B5657C12315FB3500F4F4F7 /* UsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56578B2315FB3500F4F4F7 /* UsersViewModel.swift */; };
39 | 9B5657C22315FB3500F4F4F7 /* UsersViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B56578C2315FB3500F4F4F7 /* UsersViewController.xib */; };
40 | 9B5657C32315FB3500F4F4F7 /* NewsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B56578E2315FB3500F4F4F7 /* NewsViewController.xib */; };
41 | 9B5657C42315FB3500F4F4F7 /* NewsViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56578F2315FB3500F4F4F7 /* NewsViewModelImpl.swift */; };
42 | 9B5657C52315FB3500F4F4F7 /* NewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657902315FB3500F4F4F7 /* NewsViewController.swift */; };
43 | 9B5657C62315FB3500F4F4F7 /* NewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657912315FB3500F4F4F7 /* NewsViewModel.swift */; };
44 | 9B5657C72315FB3500F4F4F7 /* LoginViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657932315FB3500F4F4F7 /* LoginViewModelImpl.swift */; };
45 | 9B5657C82315FB3500F4F4F7 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657942315FB3500F4F4F7 /* LoginViewModel.swift */; };
46 | 9B5657C92315FB3500F4F4F7 /* LoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9B5657952315FB3500F4F4F7 /* LoginViewController.xib */; };
47 | 9B5657CA2315FB3500F4F4F7 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657962315FB3500F4F4F7 /* LoginViewController.swift */; };
48 | 9B5657CB2315FB3500F4F4F7 /* BindableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657982315FB3500F4F4F7 /* BindableType.swift */; };
49 | 9B5657CC2315FB3500F4F4F7 /* NibIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657992315FB3500F4F4F7 /* NibIdentifiable.swift */; };
50 | 9B5657CD2315FB3500F4F4F7 /* DetailTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56579B2315FB3500F4F4F7 /* DetailTableViewCell.swift */; };
51 | 9B5657CE2315FB3500F4F4F7 /* Animation+Swirl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56579D2315FB3500F4F4F7 /* Animation+Swirl.swift */; };
52 | 9B5657CF2315FB3500F4F4F7 /* Animation+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56579E2315FB3500F4F4F7 /* Animation+Navigation.swift */; };
53 | 9B5657D02315FB3500F4F4F7 /* Animation+Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56579F2315FB3500F4F4F7 /* Animation+Modal.swift */; };
54 | 9B5657D12315FB3500F4F4F7 /* Animation+Fade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A02315FB3500F4F4F7 /* Animation+Fade.swift */; };
55 | 9B5657D22315FB3500F4F4F7 /* Animation+Scale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A12315FB3500F4F4F7 /* Animation+Scale.swift */; };
56 | 9B5657D32315FB3500F4F4F7 /* Presentable+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A32315FB3500F4F4F7 /* Presentable+Rx.swift */; };
57 | 9B5657D42315FB3500F4F4F7 /* TransitionAnimation+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A42315FB3500F4F4F7 /* TransitionAnimation+Defaults.swift */; };
58 | 9B5657D62315FB3500F4F4F7 /* CGAffineTransform+InPlace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A62315FB3500F4F4F7 /* CGAffineTransform+InPlace.swift */; };
59 | 9B5657D92315FB9700F4F4F7 /* Action in Frameworks */ = {isa = PBXBuildFile; productRef = 9B5657D82315FB9700F4F4F7 /* Action */; };
60 | 9B5657DC2315FC1000F4F4F7 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 9B5657DB2315FC1000F4F4F7 /* RxCocoa */; };
61 | 9BD3EBF42330CE46005861BF /* Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF32330CE46005861BF /* Transitions.swift */; };
62 | 9BD3EBFD2330D84F005861BF /* XCText+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF62330D84E005861BF /* XCText+Extras.swift */; };
63 | 9BD3EBFE2330D84F005861BF /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF72330D84F005861BF /* XCTestManifests.swift */; };
64 | 9BD3EBFF2330D84F005861BF /* TestRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF82330D84F005861BF /* TestRoute.swift */; };
65 | 9BD3EC002330D84F005861BF /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF92330D84F005861BF /* TransitionTests.swift */; };
66 | 9BD3EC012330D84F005861BF /* AnimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBFA2330D84F005861BF /* AnimationTests.swift */; };
67 | 9BD3EC022330D84F005861BF /* TestAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBFB2330D84F005861BF /* TestAnimation.swift */; };
68 | 9BD3EC042330DA3A005861BF /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EC032330DA3A005861BF /* UserService.swift */; };
69 | 9BD3EC072330DA74005861BF /* News.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EC062330DA74005861BF /* News.swift */; };
70 | 9BD3EC092330EFDF005861BF /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EC082330EFDF005861BF /* User.swift */; };
71 | 9BD3EC0E2330F372005861BF /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EC0A2330F372005861BF /* AboutViewController.swift */; };
72 | 9BD3EC0F2330F372005861BF /* AboutViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EC0B2330F372005861BF /* AboutViewModelImpl.swift */; };
73 | 9BD3EC102330F372005861BF /* AboutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EC0C2330F372005861BF /* AboutViewModel.swift */; };
74 | /* End PBXBuildFile section */
75 |
76 | /* Begin PBXContainerItemProxy section */
77 | 9B56574C2315FA7E00F4F4F7 /* PBXContainerItemProxy */ = {
78 | isa = PBXContainerItemProxy;
79 | containerPortal = 9B56572D2315FA7B00F4F4F7 /* Project object */;
80 | proxyType = 1;
81 | remoteGlobalIDString = 9B5657342315FA7B00F4F4F7;
82 | remoteInfo = "XCoordinator-Example";
83 | };
84 | 9B5657572315FA7E00F4F4F7 /* PBXContainerItemProxy */ = {
85 | isa = PBXContainerItemProxy;
86 | containerPortal = 9B56572D2315FA7B00F4F4F7 /* Project object */;
87 | proxyType = 1;
88 | remoteGlobalIDString = 9B5657342315FA7B00F4F4F7;
89 | remoteInfo = "XCoordinator-Example";
90 | };
91 | /* End PBXContainerItemProxy section */
92 |
93 | /* Begin PBXFileReference section */
94 | 9B5657352315FA7B00F4F4F7 /* XCoordinator-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "XCoordinator-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
95 | 9B5657462315FA7E00F4F4F7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
96 | 9B56574B2315FA7E00F4F4F7 /* XCoordinator-ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "XCoordinator-ExampleTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
97 | 9B5657512315FA7E00F4F4F7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
98 | 9B5657562315FA7E00F4F4F7 /* XCoordinator-ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "XCoordinator-ExampleUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
99 | 9B56575A2315FA7E00F4F4F7 /* XCoordinator_ExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCoordinator_ExampleUITests.swift; sourceTree = ""; };
100 | 9B56575C2315FA7E00F4F4F7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
101 | 9B5657692315FB3500F4F4F7 /* AppCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; };
102 | 9B56576A2315FB3500F4F4F7 /* UserListCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserListCoordinator.swift; sourceTree = ""; };
103 | 9B56576B2315FB3500F4F4F7 /* HomeSplitCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeSplitCoordinator.swift; sourceTree = ""; };
104 | 9B56576C2315FB3500F4F4F7 /* UserCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserCoordinator.swift; sourceTree = ""; };
105 | 9B56576D2315FB3500F4F4F7 /* NewsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsCoordinator.swift; sourceTree = ""; };
106 | 9B56576E2315FB3500F4F4F7 /* HomeTabCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeTabCoordinator.swift; sourceTree = ""; };
107 | 9B56576F2315FB3500F4F4F7 /* HomePageCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomePageCoordinator.swift; sourceTree = ""; };
108 | 9B5657702315FB3500F4F4F7 /* AboutCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutCoordinator.swift; sourceTree = ""; };
109 | 9B5657722315FB3500F4F4F7 /* NewsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsService.swift; sourceTree = ""; };
110 | 9B5657752315FB3500F4F4F7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
111 | 9B5657762315FB3500F4F4F7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
112 | 9B5657772315FB3500F4F4F7 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
113 | 9B56577A2315FB3500F4F4F7 /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; };
114 | 9B56577B2315FB3500F4F4F7 /* HomeViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HomeViewController.xib; sourceTree = ""; };
115 | 9B56577C2315FB3500F4F4F7 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; };
116 | 9B56577D2315FB3500F4F4F7 /* HomeViewModelImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModelImpl.swift; sourceTree = ""; };
117 | 9B56577F2315FB3500F4F4F7 /* NewsDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsDetailViewController.swift; sourceTree = ""; };
118 | 9B5657802315FB3500F4F4F7 /* NewsDetailViewModelImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsDetailViewModelImpl.swift; sourceTree = ""; };
119 | 9B5657812315FB3500F4F4F7 /* NewsDetailViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NewsDetailViewController.xib; sourceTree = ""; };
120 | 9B5657822315FB3500F4F4F7 /* NewsDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsDetailViewModel.swift; sourceTree = ""; };
121 | 9B5657842315FB3500F4F4F7 /* UserViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserViewModel.swift; sourceTree = ""; };
122 | 9B5657852315FB3500F4F4F7 /* UserViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UserViewController.xib; sourceTree = ""; };
123 | 9B5657862315FB3500F4F4F7 /* UserViewModelImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserViewModelImpl.swift; sourceTree = ""; };
124 | 9B5657872315FB3500F4F4F7 /* UserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserViewController.swift; sourceTree = ""; };
125 | 9B5657892315FB3500F4F4F7 /* UsersViewModelImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersViewModelImpl.swift; sourceTree = ""; };
126 | 9B56578A2315FB3500F4F4F7 /* UsersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersViewController.swift; sourceTree = ""; };
127 | 9B56578B2315FB3500F4F4F7 /* UsersViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersViewModel.swift; sourceTree = ""; };
128 | 9B56578C2315FB3500F4F4F7 /* UsersViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UsersViewController.xib; sourceTree = ""; };
129 | 9B56578E2315FB3500F4F4F7 /* NewsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NewsViewController.xib; sourceTree = ""; };
130 | 9B56578F2315FB3500F4F4F7 /* NewsViewModelImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsViewModelImpl.swift; sourceTree = ""; };
131 | 9B5657902315FB3500F4F4F7 /* NewsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsViewController.swift; sourceTree = ""; };
132 | 9B5657912315FB3500F4F4F7 /* NewsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsViewModel.swift; sourceTree = ""; };
133 | 9B5657932315FB3500F4F4F7 /* LoginViewModelImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewModelImpl.swift; sourceTree = ""; };
134 | 9B5657942315FB3500F4F4F7 /* LoginViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; };
135 | 9B5657952315FB3500F4F4F7 /* LoginViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LoginViewController.xib; sourceTree = ""; };
136 | 9B5657962315FB3500F4F4F7 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; };
137 | 9B5657982315FB3500F4F4F7 /* BindableType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BindableType.swift; sourceTree = ""; };
138 | 9B5657992315FB3500F4F4F7 /* NibIdentifiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibIdentifiable.swift; sourceTree = ""; };
139 | 9B56579B2315FB3500F4F4F7 /* DetailTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailTableViewCell.swift; sourceTree = ""; };
140 | 9B56579D2315FB3500F4F4F7 /* Animation+Swirl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Animation+Swirl.swift"; sourceTree = ""; };
141 | 9B56579E2315FB3500F4F4F7 /* Animation+Navigation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Animation+Navigation.swift"; sourceTree = ""; };
142 | 9B56579F2315FB3500F4F4F7 /* Animation+Modal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Animation+Modal.swift"; sourceTree = ""; };
143 | 9B5657A02315FB3500F4F4F7 /* Animation+Fade.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Animation+Fade.swift"; sourceTree = ""; };
144 | 9B5657A12315FB3500F4F4F7 /* Animation+Scale.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Animation+Scale.swift"; sourceTree = ""; };
145 | 9B5657A32315FB3500F4F4F7 /* Presentable+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Presentable+Rx.swift"; sourceTree = ""; };
146 | 9B5657A42315FB3500F4F4F7 /* TransitionAnimation+Defaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TransitionAnimation+Defaults.swift"; sourceTree = ""; };
147 | 9B5657A62315FB3500F4F4F7 /* CGAffineTransform+InPlace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGAffineTransform+InPlace.swift"; sourceTree = ""; };
148 | 9BD3EBF32330CE46005861BF /* Transitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transitions.swift; sourceTree = ""; };
149 | 9BD3EBF62330D84E005861BF /* XCText+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCText+Extras.swift"; sourceTree = ""; };
150 | 9BD3EBF72330D84F005861BF /* XCTestManifests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; };
151 | 9BD3EBF82330D84F005861BF /* TestRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestRoute.swift; sourceTree = ""; };
152 | 9BD3EBF92330D84F005861BF /* TransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionTests.swift; sourceTree = ""; };
153 | 9BD3EBFA2330D84F005861BF /* AnimationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationTests.swift; sourceTree = ""; };
154 | 9BD3EBFB2330D84F005861BF /* TestAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestAnimation.swift; sourceTree = ""; };
155 | 9BD3EC032330DA3A005861BF /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = ""; };
156 | 9BD3EC062330DA74005861BF /* News.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = News.swift; sourceTree = ""; };
157 | 9BD3EC082330EFDF005861BF /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; };
158 | 9BD3EC0A2330F372005861BF /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; };
159 | 9BD3EC0B2330F372005861BF /* AboutViewModelImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewModelImpl.swift; sourceTree = ""; };
160 | 9BD3EC0C2330F372005861BF /* AboutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewModel.swift; sourceTree = ""; };
161 | 9BD5395523315E150014DF01 /* XCoordinator-Example.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "XCoordinator-Example.xctestplan"; sourceTree = ""; };
162 | /* End PBXFileReference section */
163 |
164 | /* Begin PBXFrameworksBuildPhase section */
165 | 9B5657322315FA7B00F4F4F7 /* Frameworks */ = {
166 | isa = PBXFrameworksBuildPhase;
167 | buildActionMask = 2147483647;
168 | files = (
169 | 9B0A753A2316C1810092CA3A /* XCoordinatorRx in Frameworks */,
170 | 9B5657DC2315FC1000F4F4F7 /* RxCocoa in Frameworks */,
171 | 9B5657D92315FB9700F4F4F7 /* Action in Frameworks */,
172 | );
173 | runOnlyForDeploymentPostprocessing = 0;
174 | };
175 | 9B5657482315FA7E00F4F4F7 /* Frameworks */ = {
176 | isa = PBXFrameworksBuildPhase;
177 | buildActionMask = 2147483647;
178 | files = (
179 | );
180 | runOnlyForDeploymentPostprocessing = 0;
181 | };
182 | 9B5657532315FA7E00F4F4F7 /* Frameworks */ = {
183 | isa = PBXFrameworksBuildPhase;
184 | buildActionMask = 2147483647;
185 | files = (
186 | );
187 | runOnlyForDeploymentPostprocessing = 0;
188 | };
189 | /* End PBXFrameworksBuildPhase section */
190 |
191 | /* Begin PBXGroup section */
192 | 9B0A75382316C1810092CA3A /* Frameworks */ = {
193 | isa = PBXGroup;
194 | children = (
195 | );
196 | name = Frameworks;
197 | sourceTree = "";
198 | };
199 | 9B56572C2315FA7B00F4F4F7 = {
200 | isa = PBXGroup;
201 | children = (
202 | 9B5657372315FA7B00F4F4F7 /* XCoordinator-Example */,
203 | 9B56574E2315FA7E00F4F4F7 /* XCoordinator-ExampleTests */,
204 | 9B5657592315FA7E00F4F4F7 /* XCoordinator-ExampleUITests */,
205 | 9B5657362315FA7B00F4F4F7 /* Products */,
206 | 9B0A75382316C1810092CA3A /* Frameworks */,
207 | );
208 | sourceTree = "";
209 | };
210 | 9B5657362315FA7B00F4F4F7 /* Products */ = {
211 | isa = PBXGroup;
212 | children = (
213 | 9B5657352315FA7B00F4F4F7 /* XCoordinator-Example.app */,
214 | 9B56574B2315FA7E00F4F4F7 /* XCoordinator-ExampleTests.xctest */,
215 | 9B5657562315FA7E00F4F4F7 /* XCoordinator-ExampleUITests.xctest */,
216 | );
217 | name = Products;
218 | sourceTree = "";
219 | };
220 | 9B5657372315FA7B00F4F4F7 /* XCoordinator-Example */ = {
221 | isa = PBXGroup;
222 | children = (
223 | 9B56579C2315FB3500F4F4F7 /* Animations */,
224 | 9B5657732315FB3500F4F4F7 /* Common */,
225 | 9B5657682315FB3500F4F4F7 /* Coordinators */,
226 | 9B5657A22315FB3500F4F4F7 /* Extensions */,
227 | 9BD3EC052330DA61005861BF /* Models */,
228 | 9B5657782315FB3500F4F4F7 /* Scenes */,
229 | 9B5657712315FB3500F4F4F7 /* Services */,
230 | 9B5657972315FB3500F4F4F7 /* Utils */,
231 | 9B56579A2315FB3500F4F4F7 /* Views */,
232 | 9B5657462315FA7E00F4F4F7 /* Info.plist */,
233 | );
234 | path = "XCoordinator-Example";
235 | sourceTree = "";
236 | };
237 | 9B56574E2315FA7E00F4F4F7 /* XCoordinator-ExampleTests */ = {
238 | isa = PBXGroup;
239 | children = (
240 | 9BD5395523315E150014DF01 /* XCoordinator-Example.xctestplan */,
241 | 9BD3EBFA2330D84F005861BF /* AnimationTests.swift */,
242 | 9BD3EBFB2330D84F005861BF /* TestAnimation.swift */,
243 | 9BD3EBF82330D84F005861BF /* TestRoute.swift */,
244 | 9BD3EBF92330D84F005861BF /* TransitionTests.swift */,
245 | 9BD3EBF72330D84F005861BF /* XCTestManifests.swift */,
246 | 9BD3EBF62330D84E005861BF /* XCText+Extras.swift */,
247 | 9B5657512315FA7E00F4F4F7 /* Info.plist */,
248 | );
249 | path = "XCoordinator-ExampleTests";
250 | sourceTree = "";
251 | };
252 | 9B5657592315FA7E00F4F4F7 /* XCoordinator-ExampleUITests */ = {
253 | isa = PBXGroup;
254 | children = (
255 | 9B56575A2315FA7E00F4F4F7 /* XCoordinator_ExampleUITests.swift */,
256 | 9B56575C2315FA7E00F4F4F7 /* Info.plist */,
257 | );
258 | path = "XCoordinator-ExampleUITests";
259 | sourceTree = "";
260 | };
261 | 9B5657682315FB3500F4F4F7 /* Coordinators */ = {
262 | isa = PBXGroup;
263 | children = (
264 | 9B5657692315FB3500F4F4F7 /* AppCoordinator.swift */,
265 | 9B56576A2315FB3500F4F4F7 /* UserListCoordinator.swift */,
266 | 9B56576B2315FB3500F4F4F7 /* HomeSplitCoordinator.swift */,
267 | 9B56576C2315FB3500F4F4F7 /* UserCoordinator.swift */,
268 | 9B56576D2315FB3500F4F4F7 /* NewsCoordinator.swift */,
269 | 9B56576E2315FB3500F4F4F7 /* HomeTabCoordinator.swift */,
270 | 9B56576F2315FB3500F4F4F7 /* HomePageCoordinator.swift */,
271 | 9B5657702315FB3500F4F4F7 /* AboutCoordinator.swift */,
272 | );
273 | path = Coordinators;
274 | sourceTree = "";
275 | };
276 | 9B5657712315FB3500F4F4F7 /* Services */ = {
277 | isa = PBXGroup;
278 | children = (
279 | 9B5657722315FB3500F4F4F7 /* NewsService.swift */,
280 | 9BD3EC032330DA3A005861BF /* UserService.swift */,
281 | );
282 | path = Services;
283 | sourceTree = "";
284 | };
285 | 9B5657732315FB3500F4F4F7 /* Common */ = {
286 | isa = PBXGroup;
287 | children = (
288 | 9B5657772315FB3500F4F4F7 /* AppDelegate.swift */,
289 | 9B5657742315FB3500F4F4F7 /* LaunchScreen.xib */,
290 | 9B5657762315FB3500F4F4F7 /* Images.xcassets */,
291 | );
292 | path = Common;
293 | sourceTree = "";
294 | };
295 | 9B5657782315FB3500F4F4F7 /* Scenes */ = {
296 | isa = PBXGroup;
297 | children = (
298 | 9BD3EC132330F397005861BF /* About */,
299 | 9B5657792315FB3500F4F4F7 /* Home */,
300 | 9B5657922315FB3500F4F4F7 /* Login */,
301 | 9B56578D2315FB3500F4F4F7 /* News */,
302 | 9B56577E2315FB3500F4F4F7 /* NewsDetail */,
303 | 9B5657832315FB3500F4F4F7 /* User */,
304 | 9B5657882315FB3500F4F4F7 /* UserList */,
305 | );
306 | path = Scenes;
307 | sourceTree = "";
308 | };
309 | 9B5657792315FB3500F4F4F7 /* Home */ = {
310 | isa = PBXGroup;
311 | children = (
312 | 9B56577A2315FB3500F4F4F7 /* HomeViewModel.swift */,
313 | 9B56577B2315FB3500F4F4F7 /* HomeViewController.xib */,
314 | 9B56577C2315FB3500F4F4F7 /* HomeViewController.swift */,
315 | 9B56577D2315FB3500F4F4F7 /* HomeViewModelImpl.swift */,
316 | );
317 | path = Home;
318 | sourceTree = "";
319 | };
320 | 9B56577E2315FB3500F4F4F7 /* NewsDetail */ = {
321 | isa = PBXGroup;
322 | children = (
323 | 9B56577F2315FB3500F4F4F7 /* NewsDetailViewController.swift */,
324 | 9B5657802315FB3500F4F4F7 /* NewsDetailViewModelImpl.swift */,
325 | 9B5657812315FB3500F4F4F7 /* NewsDetailViewController.xib */,
326 | 9B5657822315FB3500F4F4F7 /* NewsDetailViewModel.swift */,
327 | );
328 | path = NewsDetail;
329 | sourceTree = "";
330 | };
331 | 9B5657832315FB3500F4F4F7 /* User */ = {
332 | isa = PBXGroup;
333 | children = (
334 | 9B5657842315FB3500F4F4F7 /* UserViewModel.swift */,
335 | 9B5657852315FB3500F4F4F7 /* UserViewController.xib */,
336 | 9B5657862315FB3500F4F4F7 /* UserViewModelImpl.swift */,
337 | 9B5657872315FB3500F4F4F7 /* UserViewController.swift */,
338 | );
339 | path = User;
340 | sourceTree = "";
341 | };
342 | 9B5657882315FB3500F4F4F7 /* UserList */ = {
343 | isa = PBXGroup;
344 | children = (
345 | 9B5657892315FB3500F4F4F7 /* UsersViewModelImpl.swift */,
346 | 9B56578A2315FB3500F4F4F7 /* UsersViewController.swift */,
347 | 9B56578B2315FB3500F4F4F7 /* UsersViewModel.swift */,
348 | 9B56578C2315FB3500F4F4F7 /* UsersViewController.xib */,
349 | );
350 | path = UserList;
351 | sourceTree = "";
352 | };
353 | 9B56578D2315FB3500F4F4F7 /* News */ = {
354 | isa = PBXGroup;
355 | children = (
356 | 9B56578E2315FB3500F4F4F7 /* NewsViewController.xib */,
357 | 9B56578F2315FB3500F4F4F7 /* NewsViewModelImpl.swift */,
358 | 9B5657902315FB3500F4F4F7 /* NewsViewController.swift */,
359 | 9B5657912315FB3500F4F4F7 /* NewsViewModel.swift */,
360 | );
361 | path = News;
362 | sourceTree = "";
363 | };
364 | 9B5657922315FB3500F4F4F7 /* Login */ = {
365 | isa = PBXGroup;
366 | children = (
367 | 9B5657932315FB3500F4F4F7 /* LoginViewModelImpl.swift */,
368 | 9B5657942315FB3500F4F4F7 /* LoginViewModel.swift */,
369 | 9B5657952315FB3500F4F4F7 /* LoginViewController.xib */,
370 | 9B5657962315FB3500F4F4F7 /* LoginViewController.swift */,
371 | );
372 | path = Login;
373 | sourceTree = "";
374 | };
375 | 9B5657972315FB3500F4F4F7 /* Utils */ = {
376 | isa = PBXGroup;
377 | children = (
378 | 9B5657982315FB3500F4F4F7 /* BindableType.swift */,
379 | 9B5657992315FB3500F4F4F7 /* NibIdentifiable.swift */,
380 | );
381 | path = Utils;
382 | sourceTree = "";
383 | };
384 | 9B56579A2315FB3500F4F4F7 /* Views */ = {
385 | isa = PBXGroup;
386 | children = (
387 | 9B56579B2315FB3500F4F4F7 /* DetailTableViewCell.swift */,
388 | );
389 | path = Views;
390 | sourceTree = "";
391 | };
392 | 9B56579C2315FB3500F4F4F7 /* Animations */ = {
393 | isa = PBXGroup;
394 | children = (
395 | 9B56579D2315FB3500F4F4F7 /* Animation+Swirl.swift */,
396 | 9B56579E2315FB3500F4F4F7 /* Animation+Navigation.swift */,
397 | 9B56579F2315FB3500F4F4F7 /* Animation+Modal.swift */,
398 | 9B5657A02315FB3500F4F4F7 /* Animation+Fade.swift */,
399 | 9B5657A12315FB3500F4F4F7 /* Animation+Scale.swift */,
400 | );
401 | path = Animations;
402 | sourceTree = "";
403 | };
404 | 9B5657A22315FB3500F4F4F7 /* Extensions */ = {
405 | isa = PBXGroup;
406 | children = (
407 | 9B5657A32315FB3500F4F4F7 /* Presentable+Rx.swift */,
408 | 9BD3EBF32330CE46005861BF /* Transitions.swift */,
409 | 9B5657A42315FB3500F4F4F7 /* TransitionAnimation+Defaults.swift */,
410 | 9B5657A62315FB3500F4F4F7 /* CGAffineTransform+InPlace.swift */,
411 | );
412 | path = Extensions;
413 | sourceTree = "";
414 | };
415 | 9BD3EC052330DA61005861BF /* Models */ = {
416 | isa = PBXGroup;
417 | children = (
418 | 9BD3EC062330DA74005861BF /* News.swift */,
419 | 9BD3EC082330EFDF005861BF /* User.swift */,
420 | );
421 | path = Models;
422 | sourceTree = "";
423 | };
424 | 9BD3EC132330F397005861BF /* About */ = {
425 | isa = PBXGroup;
426 | children = (
427 | 9BD3EC0A2330F372005861BF /* AboutViewController.swift */,
428 | 9BD3EC0B2330F372005861BF /* AboutViewModelImpl.swift */,
429 | 9BD3EC0C2330F372005861BF /* AboutViewModel.swift */,
430 | );
431 | path = About;
432 | sourceTree = "";
433 | };
434 | /* End PBXGroup section */
435 |
436 | /* Begin PBXNativeTarget section */
437 | 9B5657342315FA7B00F4F4F7 /* XCoordinator-Example */ = {
438 | isa = PBXNativeTarget;
439 | buildConfigurationList = 9B56575F2315FA7E00F4F4F7 /* Build configuration list for PBXNativeTarget "XCoordinator-Example" */;
440 | buildPhases = (
441 | 9B5657312315FA7B00F4F4F7 /* Sources */,
442 | 9B5657322315FA7B00F4F4F7 /* Frameworks */,
443 | 9B5657332315FA7B00F4F4F7 /* Resources */,
444 | );
445 | buildRules = (
446 | );
447 | dependencies = (
448 | );
449 | name = "XCoordinator-Example";
450 | packageProductDependencies = (
451 | 9B5657D82315FB9700F4F4F7 /* Action */,
452 | 9B5657DB2315FC1000F4F4F7 /* RxCocoa */,
453 | 9B0A75392316C1810092CA3A /* XCoordinatorRx */,
454 | );
455 | productName = "XCoordinator-Example";
456 | productReference = 9B5657352315FA7B00F4F4F7 /* XCoordinator-Example.app */;
457 | productType = "com.apple.product-type.application";
458 | };
459 | 9B56574A2315FA7E00F4F4F7 /* XCoordinator-ExampleTests */ = {
460 | isa = PBXNativeTarget;
461 | buildConfigurationList = 9B5657622315FA7E00F4F4F7 /* Build configuration list for PBXNativeTarget "XCoordinator-ExampleTests" */;
462 | buildPhases = (
463 | 9B5657472315FA7E00F4F4F7 /* Sources */,
464 | 9B5657482315FA7E00F4F4F7 /* Frameworks */,
465 | 9B5657492315FA7E00F4F4F7 /* Resources */,
466 | );
467 | buildRules = (
468 | );
469 | dependencies = (
470 | 9B56574D2315FA7E00F4F4F7 /* PBXTargetDependency */,
471 | );
472 | name = "XCoordinator-ExampleTests";
473 | productName = "XCoordinator-ExampleTests";
474 | productReference = 9B56574B2315FA7E00F4F4F7 /* XCoordinator-ExampleTests.xctest */;
475 | productType = "com.apple.product-type.bundle.unit-test";
476 | };
477 | 9B5657552315FA7E00F4F4F7 /* XCoordinator-ExampleUITests */ = {
478 | isa = PBXNativeTarget;
479 | buildConfigurationList = 9B5657652315FA7E00F4F4F7 /* Build configuration list for PBXNativeTarget "XCoordinator-ExampleUITests" */;
480 | buildPhases = (
481 | 9B5657522315FA7E00F4F4F7 /* Sources */,
482 | 9B5657532315FA7E00F4F4F7 /* Frameworks */,
483 | 9B5657542315FA7E00F4F4F7 /* Resources */,
484 | );
485 | buildRules = (
486 | );
487 | dependencies = (
488 | 9B5657582315FA7E00F4F4F7 /* PBXTargetDependency */,
489 | );
490 | name = "XCoordinator-ExampleUITests";
491 | productName = "XCoordinator-ExampleUITests";
492 | productReference = 9B5657562315FA7E00F4F4F7 /* XCoordinator-ExampleUITests.xctest */;
493 | productType = "com.apple.product-type.bundle.ui-testing";
494 | };
495 | /* End PBXNativeTarget section */
496 |
497 | /* Begin PBXProject section */
498 | 9B56572D2315FA7B00F4F4F7 /* Project object */ = {
499 | isa = PBXProject;
500 | attributes = {
501 | LastSwiftUpdateCheck = 1100;
502 | LastUpgradeCheck = 1100;
503 | ORGANIZATIONNAME = "QuickBird Studios";
504 | TargetAttributes = {
505 | 9B5657342315FA7B00F4F4F7 = {
506 | CreatedOnToolsVersion = 11.0;
507 | };
508 | 9B56574A2315FA7E00F4F4F7 = {
509 | CreatedOnToolsVersion = 11.0;
510 | TestTargetID = 9B5657342315FA7B00F4F4F7;
511 | };
512 | 9B5657552315FA7E00F4F4F7 = {
513 | CreatedOnToolsVersion = 11.0;
514 | TestTargetID = 9B5657342315FA7B00F4F4F7;
515 | };
516 | };
517 | };
518 | buildConfigurationList = 9B5657302315FA7B00F4F4F7 /* Build configuration list for PBXProject "XCoordinator-Example" */;
519 | compatibilityVersion = "Xcode 9.3";
520 | developmentRegion = en;
521 | hasScannedForEncodings = 0;
522 | knownRegions = (
523 | en,
524 | Base,
525 | );
526 | mainGroup = 9B56572C2315FA7B00F4F4F7;
527 | packageReferences = (
528 | 9B5657D72315FB9700F4F4F7 /* XCRemoteSwiftPackageReference "Action" */,
529 | 9B5657DA2315FC1000F4F4F7 /* XCRemoteSwiftPackageReference "RxSwift" */,
530 | 9B5657DD2315FC5C00F4F4F7 /* XCRemoteSwiftPackageReference "XCoordinator" */,
531 | );
532 | productRefGroup = 9B5657362315FA7B00F4F4F7 /* Products */;
533 | projectDirPath = "";
534 | projectRoot = "";
535 | targets = (
536 | 9B5657342315FA7B00F4F4F7 /* XCoordinator-Example */,
537 | 9B56574A2315FA7E00F4F4F7 /* XCoordinator-ExampleTests */,
538 | 9B5657552315FA7E00F4F4F7 /* XCoordinator-ExampleUITests */,
539 | );
540 | };
541 | /* End PBXProject section */
542 |
543 | /* Begin PBXResourcesBuildPhase section */
544 | 9B5657332315FA7B00F4F4F7 /* Resources */ = {
545 | isa = PBXResourcesBuildPhase;
546 | buildActionMask = 2147483647;
547 | files = (
548 | 9B5657C92315FB3500F4F4F7 /* LoginViewController.xib in Resources */,
549 | 9B5657BC2315FB3500F4F4F7 /* UserViewController.xib in Resources */,
550 | 9B5657C22315FB3500F4F4F7 /* UsersViewController.xib in Resources */,
551 | 9B5657B92315FB3500F4F4F7 /* NewsDetailViewController.xib in Resources */,
552 | 9B5657B12315FB3500F4F4F7 /* Images.xcassets in Resources */,
553 | 9B5657B42315FB3500F4F4F7 /* HomeViewController.xib in Resources */,
554 | 9B5657B02315FB3500F4F4F7 /* LaunchScreen.xib in Resources */,
555 | 9B5657C32315FB3500F4F4F7 /* NewsViewController.xib in Resources */,
556 | );
557 | runOnlyForDeploymentPostprocessing = 0;
558 | };
559 | 9B5657492315FA7E00F4F4F7 /* Resources */ = {
560 | isa = PBXResourcesBuildPhase;
561 | buildActionMask = 2147483647;
562 | files = (
563 | );
564 | runOnlyForDeploymentPostprocessing = 0;
565 | };
566 | 9B5657542315FA7E00F4F4F7 /* Resources */ = {
567 | isa = PBXResourcesBuildPhase;
568 | buildActionMask = 2147483647;
569 | files = (
570 | );
571 | runOnlyForDeploymentPostprocessing = 0;
572 | };
573 | /* End PBXResourcesBuildPhase section */
574 |
575 | /* Begin PBXSourcesBuildPhase section */
576 | 9B5657312315FA7B00F4F4F7 /* Sources */ = {
577 | isa = PBXSourcesBuildPhase;
578 | buildActionMask = 2147483647;
579 | files = (
580 | 9B5657CA2315FB3500F4F4F7 /* LoginViewController.swift in Sources */,
581 | 9B5657BA2315FB3500F4F4F7 /* NewsDetailViewModel.swift in Sources */,
582 | 9BD3EC0E2330F372005861BF /* AboutViewController.swift in Sources */,
583 | 9B5657B82315FB3500F4F4F7 /* NewsDetailViewModelImpl.swift in Sources */,
584 | 9BD3EBF42330CE46005861BF /* Transitions.swift in Sources */,
585 | 9B5657B62315FB3500F4F4F7 /* HomeViewModelImpl.swift in Sources */,
586 | 9B5657CF2315FB3500F4F4F7 /* Animation+Navigation.swift in Sources */,
587 | 9B5657C42315FB3500F4F4F7 /* NewsViewModelImpl.swift in Sources */,
588 | 9B5657AE2315FB3500F4F4F7 /* AboutCoordinator.swift in Sources */,
589 | 9B5657D22315FB3500F4F4F7 /* Animation+Scale.swift in Sources */,
590 | 9B5657B32315FB3500F4F4F7 /* HomeViewModel.swift in Sources */,
591 | 9B5657A72315FB3500F4F4F7 /* AppCoordinator.swift in Sources */,
592 | 9B5657AD2315FB3500F4F4F7 /* HomePageCoordinator.swift in Sources */,
593 | 9BD3EC0F2330F372005861BF /* AboutViewModelImpl.swift in Sources */,
594 | 9B5657C82315FB3500F4F4F7 /* LoginViewModel.swift in Sources */,
595 | 9B5657AF2315FB3500F4F4F7 /* NewsService.swift in Sources */,
596 | 9B5657D32315FB3500F4F4F7 /* Presentable+Rx.swift in Sources */,
597 | 9B5657AC2315FB3500F4F4F7 /* HomeTabCoordinator.swift in Sources */,
598 | 9B5657CE2315FB3500F4F4F7 /* Animation+Swirl.swift in Sources */,
599 | 9B5657A82315FB3500F4F4F7 /* UserListCoordinator.swift in Sources */,
600 | 9BD3EC072330DA74005861BF /* News.swift in Sources */,
601 | 9B5657C02315FB3500F4F4F7 /* UsersViewController.swift in Sources */,
602 | 9B5657C62315FB3500F4F4F7 /* NewsViewModel.swift in Sources */,
603 | 9B5657D42315FB3500F4F4F7 /* TransitionAnimation+Defaults.swift in Sources */,
604 | 9B5657C12315FB3500F4F4F7 /* UsersViewModel.swift in Sources */,
605 | 9B5657A92315FB3500F4F4F7 /* HomeSplitCoordinator.swift in Sources */,
606 | 9B5657AB2315FB3500F4F4F7 /* NewsCoordinator.swift in Sources */,
607 | 9B5657D62315FB3500F4F4F7 /* CGAffineTransform+InPlace.swift in Sources */,
608 | 9B5657C52315FB3500F4F4F7 /* NewsViewController.swift in Sources */,
609 | 9B5657B72315FB3500F4F4F7 /* NewsDetailViewController.swift in Sources */,
610 | 9B5657C72315FB3500F4F4F7 /* LoginViewModelImpl.swift in Sources */,
611 | 9B5657CB2315FB3500F4F4F7 /* BindableType.swift in Sources */,
612 | 9BD3EC102330F372005861BF /* AboutViewModel.swift in Sources */,
613 | 9B5657D12315FB3500F4F4F7 /* Animation+Fade.swift in Sources */,
614 | 9B5657B22315FB3500F4F4F7 /* AppDelegate.swift in Sources */,
615 | 9B5657BB2315FB3500F4F4F7 /* UserViewModel.swift in Sources */,
616 | 9B5657BF2315FB3500F4F4F7 /* UsersViewModelImpl.swift in Sources */,
617 | 9B5657AA2315FB3500F4F4F7 /* UserCoordinator.swift in Sources */,
618 | 9B5657B52315FB3500F4F4F7 /* HomeViewController.swift in Sources */,
619 | 9B5657CC2315FB3500F4F4F7 /* NibIdentifiable.swift in Sources */,
620 | 9B5657D02315FB3500F4F4F7 /* Animation+Modal.swift in Sources */,
621 | 9B5657BD2315FB3500F4F4F7 /* UserViewModelImpl.swift in Sources */,
622 | 9B5657BE2315FB3500F4F4F7 /* UserViewController.swift in Sources */,
623 | 9BD3EC042330DA3A005861BF /* UserService.swift in Sources */,
624 | 9B5657CD2315FB3500F4F4F7 /* DetailTableViewCell.swift in Sources */,
625 | 9BD3EC092330EFDF005861BF /* User.swift in Sources */,
626 | );
627 | runOnlyForDeploymentPostprocessing = 0;
628 | };
629 | 9B5657472315FA7E00F4F4F7 /* Sources */ = {
630 | isa = PBXSourcesBuildPhase;
631 | buildActionMask = 2147483647;
632 | files = (
633 | 9BD3EC022330D84F005861BF /* TestAnimation.swift in Sources */,
634 | 9BD3EC012330D84F005861BF /* AnimationTests.swift in Sources */,
635 | 9BD3EBFD2330D84F005861BF /* XCText+Extras.swift in Sources */,
636 | 9BD3EBFF2330D84F005861BF /* TestRoute.swift in Sources */,
637 | 9BD3EBFE2330D84F005861BF /* XCTestManifests.swift in Sources */,
638 | 9BD3EC002330D84F005861BF /* TransitionTests.swift in Sources */,
639 | );
640 | runOnlyForDeploymentPostprocessing = 0;
641 | };
642 | 9B5657522315FA7E00F4F4F7 /* Sources */ = {
643 | isa = PBXSourcesBuildPhase;
644 | buildActionMask = 2147483647;
645 | files = (
646 | 9B56575B2315FA7E00F4F4F7 /* XCoordinator_ExampleUITests.swift in Sources */,
647 | );
648 | runOnlyForDeploymentPostprocessing = 0;
649 | };
650 | /* End PBXSourcesBuildPhase section */
651 |
652 | /* Begin PBXTargetDependency section */
653 | 9B56574D2315FA7E00F4F4F7 /* PBXTargetDependency */ = {
654 | isa = PBXTargetDependency;
655 | target = 9B5657342315FA7B00F4F4F7 /* XCoordinator-Example */;
656 | targetProxy = 9B56574C2315FA7E00F4F4F7 /* PBXContainerItemProxy */;
657 | };
658 | 9B5657582315FA7E00F4F4F7 /* PBXTargetDependency */ = {
659 | isa = PBXTargetDependency;
660 | target = 9B5657342315FA7B00F4F4F7 /* XCoordinator-Example */;
661 | targetProxy = 9B5657572315FA7E00F4F4F7 /* PBXContainerItemProxy */;
662 | };
663 | /* End PBXTargetDependency section */
664 |
665 | /* Begin PBXVariantGroup section */
666 | 9B5657742315FB3500F4F4F7 /* LaunchScreen.xib */ = {
667 | isa = PBXVariantGroup;
668 | children = (
669 | 9B5657752315FB3500F4F4F7 /* Base */,
670 | );
671 | name = LaunchScreen.xib;
672 | sourceTree = "";
673 | };
674 | /* End PBXVariantGroup section */
675 |
676 | /* Begin XCBuildConfiguration section */
677 | 9B56575D2315FA7E00F4F4F7 /* Debug */ = {
678 | isa = XCBuildConfiguration;
679 | buildSettings = {
680 | ALWAYS_SEARCH_USER_PATHS = NO;
681 | CLANG_ANALYZER_NONNULL = YES;
682 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
683 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
684 | CLANG_CXX_LIBRARY = "libc++";
685 | CLANG_ENABLE_MODULES = YES;
686 | CLANG_ENABLE_OBJC_ARC = YES;
687 | CLANG_ENABLE_OBJC_WEAK = YES;
688 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
689 | CLANG_WARN_BOOL_CONVERSION = YES;
690 | CLANG_WARN_COMMA = YES;
691 | CLANG_WARN_CONSTANT_CONVERSION = YES;
692 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
693 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
694 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
695 | CLANG_WARN_EMPTY_BODY = YES;
696 | CLANG_WARN_ENUM_CONVERSION = YES;
697 | CLANG_WARN_INFINITE_RECURSION = YES;
698 | CLANG_WARN_INT_CONVERSION = YES;
699 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
700 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
701 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
702 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
703 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
704 | CLANG_WARN_STRICT_PROTOTYPES = YES;
705 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
706 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
707 | CLANG_WARN_UNREACHABLE_CODE = YES;
708 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
709 | COPY_PHASE_STRIP = NO;
710 | DEBUG_INFORMATION_FORMAT = dwarf;
711 | ENABLE_STRICT_OBJC_MSGSEND = YES;
712 | ENABLE_TESTABILITY = YES;
713 | GCC_C_LANGUAGE_STANDARD = gnu11;
714 | GCC_DYNAMIC_NO_PIC = NO;
715 | GCC_NO_COMMON_BLOCKS = YES;
716 | GCC_OPTIMIZATION_LEVEL = 0;
717 | GCC_PREPROCESSOR_DEFINITIONS = (
718 | "DEBUG=1",
719 | "$(inherited)",
720 | );
721 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
722 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
723 | GCC_WARN_UNDECLARED_SELECTOR = YES;
724 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
725 | GCC_WARN_UNUSED_FUNCTION = YES;
726 | GCC_WARN_UNUSED_VARIABLE = YES;
727 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
728 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
729 | MTL_FAST_MATH = YES;
730 | ONLY_ACTIVE_ARCH = YES;
731 | SDKROOT = iphoneos;
732 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
733 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
734 | };
735 | name = Debug;
736 | };
737 | 9B56575E2315FA7E00F4F4F7 /* Release */ = {
738 | isa = XCBuildConfiguration;
739 | buildSettings = {
740 | ALWAYS_SEARCH_USER_PATHS = NO;
741 | CLANG_ANALYZER_NONNULL = YES;
742 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
743 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
744 | CLANG_CXX_LIBRARY = "libc++";
745 | CLANG_ENABLE_MODULES = YES;
746 | CLANG_ENABLE_OBJC_ARC = YES;
747 | CLANG_ENABLE_OBJC_WEAK = YES;
748 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
749 | CLANG_WARN_BOOL_CONVERSION = YES;
750 | CLANG_WARN_COMMA = YES;
751 | CLANG_WARN_CONSTANT_CONVERSION = YES;
752 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
753 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
754 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
755 | CLANG_WARN_EMPTY_BODY = YES;
756 | CLANG_WARN_ENUM_CONVERSION = YES;
757 | CLANG_WARN_INFINITE_RECURSION = YES;
758 | CLANG_WARN_INT_CONVERSION = YES;
759 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
760 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
761 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
762 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
763 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
764 | CLANG_WARN_STRICT_PROTOTYPES = YES;
765 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
766 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
767 | CLANG_WARN_UNREACHABLE_CODE = YES;
768 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
769 | COPY_PHASE_STRIP = NO;
770 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
771 | ENABLE_NS_ASSERTIONS = NO;
772 | ENABLE_STRICT_OBJC_MSGSEND = YES;
773 | GCC_C_LANGUAGE_STANDARD = gnu11;
774 | GCC_NO_COMMON_BLOCKS = YES;
775 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
776 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
777 | GCC_WARN_UNDECLARED_SELECTOR = YES;
778 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
779 | GCC_WARN_UNUSED_FUNCTION = YES;
780 | GCC_WARN_UNUSED_VARIABLE = YES;
781 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
782 | MTL_ENABLE_DEBUG_INFO = NO;
783 | MTL_FAST_MATH = YES;
784 | SDKROOT = iphoneos;
785 | SWIFT_COMPILATION_MODE = wholemodule;
786 | SWIFT_OPTIMIZATION_LEVEL = "-O";
787 | VALIDATE_PRODUCT = YES;
788 | };
789 | name = Release;
790 | };
791 | 9B5657602315FA7E00F4F4F7 /* Debug */ = {
792 | isa = XCBuildConfiguration;
793 | buildSettings = {
794 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
795 | CODE_SIGN_STYLE = Automatic;
796 | DEVELOPMENT_TEAM = 77E79NGPCV;
797 | INFOPLIST_FILE = "XCoordinator-Example/Info.plist";
798 | LD_RUNPATH_SEARCH_PATHS = (
799 | "$(inherited)",
800 | "@executable_path/Frameworks",
801 | );
802 | PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.XCoordinator-Example";
803 | PRODUCT_NAME = "$(TARGET_NAME)";
804 | SWIFT_VERSION = 5.0;
805 | TARGETED_DEVICE_FAMILY = "1,2";
806 | };
807 | name = Debug;
808 | };
809 | 9B5657612315FA7E00F4F4F7 /* Release */ = {
810 | isa = XCBuildConfiguration;
811 | buildSettings = {
812 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
813 | CODE_SIGN_STYLE = Automatic;
814 | DEVELOPMENT_TEAM = 77E79NGPCV;
815 | INFOPLIST_FILE = "XCoordinator-Example/Info.plist";
816 | LD_RUNPATH_SEARCH_PATHS = (
817 | "$(inherited)",
818 | "@executable_path/Frameworks",
819 | );
820 | PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.XCoordinator-Example";
821 | PRODUCT_NAME = "$(TARGET_NAME)";
822 | SWIFT_VERSION = 5.0;
823 | TARGETED_DEVICE_FAMILY = "1,2";
824 | };
825 | name = Release;
826 | };
827 | 9B5657632315FA7E00F4F4F7 /* Debug */ = {
828 | isa = XCBuildConfiguration;
829 | buildSettings = {
830 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
831 | BUNDLE_LOADER = "$(TEST_HOST)";
832 | CODE_SIGN_STYLE = Automatic;
833 | DEVELOPMENT_TEAM = 77E79NGPCV;
834 | INFOPLIST_FILE = "XCoordinator-ExampleTests/Info.plist";
835 | LD_RUNPATH_SEARCH_PATHS = (
836 | "$(inherited)",
837 | "@executable_path/Frameworks",
838 | "@loader_path/Frameworks",
839 | );
840 | PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.XCoordinator-ExampleTests";
841 | PRODUCT_NAME = "$(TARGET_NAME)";
842 | SWIFT_VERSION = 5.0;
843 | TARGETED_DEVICE_FAMILY = "1,2";
844 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XCoordinator-Example.app/XCoordinator-Example";
845 | };
846 | name = Debug;
847 | };
848 | 9B5657642315FA7E00F4F4F7 /* Release */ = {
849 | isa = XCBuildConfiguration;
850 | buildSettings = {
851 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
852 | BUNDLE_LOADER = "$(TEST_HOST)";
853 | CODE_SIGN_STYLE = Automatic;
854 | DEVELOPMENT_TEAM = 77E79NGPCV;
855 | INFOPLIST_FILE = "XCoordinator-ExampleTests/Info.plist";
856 | LD_RUNPATH_SEARCH_PATHS = (
857 | "$(inherited)",
858 | "@executable_path/Frameworks",
859 | "@loader_path/Frameworks",
860 | );
861 | PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.XCoordinator-ExampleTests";
862 | PRODUCT_NAME = "$(TARGET_NAME)";
863 | SWIFT_VERSION = 5.0;
864 | TARGETED_DEVICE_FAMILY = "1,2";
865 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XCoordinator-Example.app/XCoordinator-Example";
866 | };
867 | name = Release;
868 | };
869 | 9B5657662315FA7E00F4F4F7 /* Debug */ = {
870 | isa = XCBuildConfiguration;
871 | buildSettings = {
872 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
873 | CODE_SIGN_STYLE = Automatic;
874 | DEVELOPMENT_TEAM = 77E79NGPCV;
875 | INFOPLIST_FILE = "XCoordinator-ExampleUITests/Info.plist";
876 | LD_RUNPATH_SEARCH_PATHS = (
877 | "$(inherited)",
878 | "@executable_path/Frameworks",
879 | "@loader_path/Frameworks",
880 | );
881 | PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.XCoordinator-ExampleUITests";
882 | PRODUCT_NAME = "$(TARGET_NAME)";
883 | SWIFT_VERSION = 5.0;
884 | TARGETED_DEVICE_FAMILY = "1,2";
885 | TEST_TARGET_NAME = "XCoordinator-Example";
886 | };
887 | name = Debug;
888 | };
889 | 9B5657672315FA7E00F4F4F7 /* Release */ = {
890 | isa = XCBuildConfiguration;
891 | buildSettings = {
892 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
893 | CODE_SIGN_STYLE = Automatic;
894 | DEVELOPMENT_TEAM = 77E79NGPCV;
895 | INFOPLIST_FILE = "XCoordinator-ExampleUITests/Info.plist";
896 | LD_RUNPATH_SEARCH_PATHS = (
897 | "$(inherited)",
898 | "@executable_path/Frameworks",
899 | "@loader_path/Frameworks",
900 | );
901 | PRODUCT_BUNDLE_IDENTIFIER = "com.quickbirdstudios.XCoordinator-ExampleUITests";
902 | PRODUCT_NAME = "$(TARGET_NAME)";
903 | SWIFT_VERSION = 5.0;
904 | TARGETED_DEVICE_FAMILY = "1,2";
905 | TEST_TARGET_NAME = "XCoordinator-Example";
906 | };
907 | name = Release;
908 | };
909 | /* End XCBuildConfiguration section */
910 |
911 | /* Begin XCConfigurationList section */
912 | 9B5657302315FA7B00F4F4F7 /* Build configuration list for PBXProject "XCoordinator-Example" */ = {
913 | isa = XCConfigurationList;
914 | buildConfigurations = (
915 | 9B56575D2315FA7E00F4F4F7 /* Debug */,
916 | 9B56575E2315FA7E00F4F4F7 /* Release */,
917 | );
918 | defaultConfigurationIsVisible = 0;
919 | defaultConfigurationName = Release;
920 | };
921 | 9B56575F2315FA7E00F4F4F7 /* Build configuration list for PBXNativeTarget "XCoordinator-Example" */ = {
922 | isa = XCConfigurationList;
923 | buildConfigurations = (
924 | 9B5657602315FA7E00F4F4F7 /* Debug */,
925 | 9B5657612315FA7E00F4F4F7 /* Release */,
926 | );
927 | defaultConfigurationIsVisible = 0;
928 | defaultConfigurationName = Release;
929 | };
930 | 9B5657622315FA7E00F4F4F7 /* Build configuration list for PBXNativeTarget "XCoordinator-ExampleTests" */ = {
931 | isa = XCConfigurationList;
932 | buildConfigurations = (
933 | 9B5657632315FA7E00F4F4F7 /* Debug */,
934 | 9B5657642315FA7E00F4F4F7 /* Release */,
935 | );
936 | defaultConfigurationIsVisible = 0;
937 | defaultConfigurationName = Release;
938 | };
939 | 9B5657652315FA7E00F4F4F7 /* Build configuration list for PBXNativeTarget "XCoordinator-ExampleUITests" */ = {
940 | isa = XCConfigurationList;
941 | buildConfigurations = (
942 | 9B5657662315FA7E00F4F4F7 /* Debug */,
943 | 9B5657672315FA7E00F4F4F7 /* Release */,
944 | );
945 | defaultConfigurationIsVisible = 0;
946 | defaultConfigurationName = Release;
947 | };
948 | /* End XCConfigurationList section */
949 |
950 | /* Begin XCRemoteSwiftPackageReference section */
951 | 9B5657D72315FB9700F4F4F7 /* XCRemoteSwiftPackageReference "Action" */ = {
952 | isa = XCRemoteSwiftPackageReference;
953 | repositoryURL = "https://github.com/RxSwiftCommunity/Action.git";
954 | requirement = {
955 | kind = upToNextMajorVersion;
956 | minimumVersion = 4.0.1;
957 | };
958 | };
959 | 9B5657DA2315FC1000F4F4F7 /* XCRemoteSwiftPackageReference "RxSwift" */ = {
960 | isa = XCRemoteSwiftPackageReference;
961 | repositoryURL = "https://github.com/ReactiveX/RxSwift.git";
962 | requirement = {
963 | kind = upToNextMajorVersion;
964 | minimumVersion = 5.0.1;
965 | };
966 | };
967 | 9B5657DD2315FC5C00F4F4F7 /* XCRemoteSwiftPackageReference "XCoordinator" */ = {
968 | isa = XCRemoteSwiftPackageReference;
969 | repositoryURL = "https://github.com/quickbirdstudios/XCoordinator.git";
970 | requirement = {
971 | kind = upToNextMajorVersion;
972 | minimumVersion = 2.0.2;
973 | };
974 | };
975 | /* End XCRemoteSwiftPackageReference section */
976 |
977 | /* Begin XCSwiftPackageProductDependency section */
978 | 9B0A75392316C1810092CA3A /* XCoordinatorRx */ = {
979 | isa = XCSwiftPackageProductDependency;
980 | package = 9B5657DD2315FC5C00F4F4F7 /* XCRemoteSwiftPackageReference "XCoordinator" */;
981 | productName = XCoordinatorRx;
982 | };
983 | 9B5657D82315FB9700F4F4F7 /* Action */ = {
984 | isa = XCSwiftPackageProductDependency;
985 | package = 9B5657D72315FB9700F4F4F7 /* XCRemoteSwiftPackageReference "Action" */;
986 | productName = Action;
987 | };
988 | 9B5657DB2315FC1000F4F4F7 /* RxCocoa */ = {
989 | isa = XCSwiftPackageProductDependency;
990 | package = 9B5657DA2315FC1000F4F4F7 /* XCRemoteSwiftPackageReference "RxSwift" */;
991 | productName = RxCocoa;
992 | };
993 | /* End XCSwiftPackageProductDependency section */
994 | };
995 | rootObject = 9B56572D2315FA7B00F4F4F7 /* Project object */;
996 | }
997 |
--------------------------------------------------------------------------------
/XCoordinator-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/XCoordinator-Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/XCoordinator-Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Action",
6 | "repositoryURL": "https://github.com/RxSwiftCommunity/Action.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "cdade63f7bbe1f5e1eff7779e5858a796dc2c001",
10 | "version": "4.0.1"
11 | }
12 | },
13 | {
14 | "package": "RxSwift",
15 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "b3e888b4972d9bc76495dd74d30a8c7fad4b9395",
19 | "version": "5.0.1"
20 | }
21 | },
22 | {
23 | "package": "XCoordinator",
24 | "repositoryURL": "https://github.com/quickbirdstudios/XCoordinator.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "0c16cc7061f93d278279137277efb13385e960a6",
28 | "version": "2.0.5"
29 | }
30 | }
31 | ]
32 | },
33 | "version": 1
34 | }
35 |
--------------------------------------------------------------------------------
/XCoordinator-Example.xcodeproj/xcshareddata/xcschemes/XCoordinator-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
34 |
35 |
36 |
37 |
39 |
45 |
46 |
47 |
49 |
55 |
56 |
57 |
58 |
59 |
69 |
71 |
77 |
78 |
79 |
80 |
84 |
85 |
89 |
90 |
91 |
92 |
98 |
100 |
106 |
107 |
108 |
109 |
111 |
112 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Animations/Animation+Fade.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Animation+Fade.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.12.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | extension Animation {
13 | static let fade = Animation(
14 | presentation: InteractiveTransitionAnimation.fade,
15 | dismissal: InteractiveTransitionAnimation.fade
16 | )
17 | }
18 |
19 | extension InteractiveTransitionAnimation {
20 | fileprivate static let fade = InteractiveTransitionAnimation(duration: defaultAnimationDuration) { transitionContext in
21 | let containerView = transitionContext.containerView
22 | let toView = transitionContext.view(forKey: .to)!
23 |
24 | toView.alpha = 0.0
25 | containerView.addSubview(toView)
26 |
27 | UIView.animate(withDuration: defaultAnimationDuration, delay: 0, options: [.curveLinear], animations: {
28 | toView.alpha = 1.0
29 | }, completion: { _ in
30 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Animations/Animation+Modal.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Animation+Modal.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 19.01.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | extension Animation {
13 | static let modal = Animation(presentation: InteractiveTransitionAnimation.modalPresentation,
14 | dismissal: InteractiveTransitionAnimation.modalDismissal)
15 | }
16 |
17 | extension InteractiveTransitionAnimation {
18 | private static let duration: TimeInterval = 0.35
19 |
20 | fileprivate static let modalPresentation = InteractiveTransitionAnimation(duration: duration) { context in
21 | let toView: UIView = context.view(forKey: .to)!
22 | let fromView: UIView = context.view(forKey: .from)!
23 |
24 | var startToFrame = fromView.frame
25 | startToFrame.origin.y += startToFrame.height
26 | context.containerView.addSubview(toView)
27 | context.containerView.bringSubviewToFront(toView)
28 | toView.frame = startToFrame
29 |
30 | UIView.animate(withDuration: duration, animations: {
31 | toView.frame = fromView.frame
32 | }, completion: { _ in
33 | context.completeTransition(!context.transitionWasCancelled)
34 | })
35 | }
36 |
37 | fileprivate static let modalDismissal = InteractiveTransitionAnimation(duration: duration) { context in
38 | let toView: UIView = context.view(forKey: .to)!
39 | let fromView: UIView = context.view(forKey: .from)!
40 |
41 | context.containerView.addSubview(toView)
42 | context.containerView.sendSubviewToBack(toView)
43 | var newFromFrame = toView.frame
44 | newFromFrame.origin.y += toView.frame.height
45 |
46 | UIView.animate(withDuration: duration, animations: {
47 | fromView.frame = newFromFrame
48 | }, completion: { _ in
49 | context.completeTransition(!context.transitionWasCancelled)
50 | })
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Animations/Animation+Navigation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Animation+Navigation.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 18.01.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | // swiftlint:disable force_unwrapping
13 |
14 | extension Animation {
15 | static let navigation = Animation(presentation: InteractiveTransitionAnimation.push,
16 | dismissal: InteractiveTransitionAnimation.pop)
17 | }
18 |
19 | extension InteractiveTransitionAnimation {
20 | private static let duration: TimeInterval = 0.25
21 |
22 | fileprivate static let push = InteractiveTransitionAnimation(duration: duration) { context in
23 | let toView = context.view(forKey: .to)!
24 | let fromView = context.view(forKey: .from)!
25 |
26 | let middleFrame = fromView.frame
27 |
28 | var leftFrame = middleFrame
29 | leftFrame.origin.x -= middleFrame.width * 0.3
30 |
31 | var rightFrame = middleFrame
32 | rightFrame.origin.x += middleFrame.width
33 |
34 | context.containerView.addSubview(toView)
35 | context.containerView.bringSubviewToFront(toView)
36 | toView.frame = rightFrame
37 |
38 | UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
39 | fromView.frame = leftFrame
40 | toView.frame = middleFrame
41 | }, completion: { _ in
42 | context.completeTransition(!context.transitionWasCancelled)
43 | })
44 | }
45 |
46 | fileprivate static let pop = InteractiveTransitionAnimation(duration: duration) { context in
47 | let toView = context.view(forKey: .to)!
48 | let fromView = context.view(forKey: .from)!
49 |
50 | let middleFrame = fromView.frame
51 |
52 | var leftFrame = middleFrame
53 | leftFrame.origin.x -= middleFrame.width * 0.3
54 |
55 | var rightFrame = middleFrame
56 | rightFrame.origin.x += middleFrame.width
57 |
58 | context.containerView.addSubview(toView)
59 | context.containerView.sendSubviewToBack(toView)
60 | toView.frame = leftFrame
61 |
62 | UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
63 | fromView.frame = rightFrame
64 | toView.frame = middleFrame
65 | }, completion: { _ in
66 | context.completeTransition(!context.transitionWasCancelled)
67 | })
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Animations/Animation+Scale.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Animation+Scale.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.12.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | extension Animation {
13 | static let scale = Animation(
14 | presentation: InteractiveTransitionAnimation.scalePresentation,
15 | dismissal: InteractiveTransitionAnimation.scaleDismissal
16 | )
17 | }
18 |
19 | extension InteractiveTransitionAnimation {
20 | fileprivate static let scalePresentation = InteractiveTransitionAnimation(duration: defaultAnimationDuration) { transitionContext in
21 | let containerView = transitionContext.containerView
22 | let toView = transitionContext.view(forKey: .to)!
23 | let fromView = transitionContext.view(forKey: .from)!
24 |
25 | containerView.backgroundColor = .white
26 | toView.transform = CGAffineTransform(scaleX: .verySmall, y: .verySmall)
27 | toView.alpha = 0
28 |
29 | containerView.addSubview(toView)
30 | containerView.bringSubviewToFront(toView)
31 |
32 | UIView.animate(withDuration: defaultAnimationDuration, animations: {
33 | toView.transform = .identity
34 |
35 | toView.alpha = 1
36 | fromView.alpha = 0
37 | }, completion: { _ in
38 | fromView.alpha = 1
39 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
40 | })
41 | }
42 |
43 | fileprivate static let scaleDismissal = InteractiveTransitionAnimation(duration: defaultAnimationDuration) { transitionContext in
44 | let containerView: UIView = transitionContext.containerView
45 | let toView: UIView = transitionContext.view(forKey: .to)!
46 | let fromView: UIView = transitionContext.view(forKey: .from)!
47 |
48 | containerView.backgroundColor = .white
49 | containerView.addSubview(toView)
50 | containerView.sendSubviewToBack(toView)
51 |
52 | toView.alpha = 0
53 | fromView.layer.masksToBounds = true
54 | let cornerRadius = max(fromView.frame.height, fromView.frame.width)
55 |
56 | UIView.animate(withDuration: defaultAnimationDuration, animations: {
57 | fromView.transform.scale(by: .verySmall)
58 |
59 | toView.alpha = 1
60 | fromView.alpha = 0
61 | }, completion: { _ in
62 | if !transitionContext.transitionWasCancelled {
63 | fromView.removeFromSuperview()
64 | }
65 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
66 | })
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Animations/Animation+Swirl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Animation+Swirl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.12.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | extension Animation {
13 |
14 | @available(iOS 10.0, *)
15 | static let swirl = Animation(presentation: InterruptibleTransitionAnimation.swirlPresentation,
16 | dismissal: InterruptibleTransitionAnimation.swirlDismissal)
17 | }
18 |
19 | @available(iOS 10.0, *)
20 | extension InterruptibleTransitionAnimation {
21 | fileprivate static let swirlPresentation = InterruptibleTransitionAnimation(duration: defaultAnimationDuration) { transitionContext in
22 | let containerView = transitionContext.containerView
23 | let toView = transitionContext.view(forKey: .to)!
24 | let fromView = transitionContext.view(forKey: .from)!
25 |
26 | containerView.backgroundColor = .white
27 | toView.transform = CGAffineTransform(scaleX: .verySmall, y: .verySmall)
28 | toView.alpha = 0
29 |
30 | containerView.addSubview(toView)
31 | containerView.bringSubviewToFront(toView)
32 |
33 | let animator = UIViewPropertyAnimator(duration: defaultAnimationDuration, curve: .easeInOut) {
34 | toView.transform = .identity
35 | toView.transform.rotate(by: .pi - .verySmall)
36 | toView.transform.rotate(by: .pi - .verySmall)
37 |
38 | toView.alpha = 1
39 | fromView.alpha = 0
40 | }
41 |
42 | animator.addCompletion { _ in
43 | fromView.alpha = 1
44 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
45 | }
46 |
47 | return animator
48 | }
49 |
50 | fileprivate static let swirlDismissal = InterruptibleTransitionAnimation(duration: defaultAnimationDuration) { transitionContext in
51 | let containerView: UIView = transitionContext.containerView
52 | let toView: UIView = transitionContext.view(forKey: .to)!
53 | let fromView: UIView = transitionContext.view(forKey: .from)!
54 |
55 | containerView.backgroundColor = .white
56 | containerView.addSubview(toView)
57 | containerView.sendSubviewToBack(toView)
58 |
59 | toView.alpha = 0
60 |
61 | let animator = UIViewPropertyAnimator(duration: defaultAnimationDuration, curve: .easeInOut) {
62 | fromView.transform.scale(by: .verySmall)
63 | fromView.transform.rotate(by: -.pi + .verySmall)
64 | fromView.transform.rotate(by: -.pi + .verySmall)
65 |
66 | toView.alpha = 1
67 | fromView.alpha = 0
68 | }
69 |
70 | animator.addCompletion { _ in
71 | if !transitionContext.transitionWasCancelled {
72 | fromView.removeFromSuperview()
73 | }
74 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
75 | }
76 |
77 | return animator
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 03.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | // MARK: Stored properties
15 |
16 | private lazy var mainWindow = UIWindow()
17 | private let router = AppCoordinator().strongRouter
18 |
19 | // MARK: UIApplicationDelegate
20 |
21 | func application(_ application: UIApplication,
22 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
23 | configureUI()
24 | router.setRoot(for: mainWindow)
25 | return true
26 | }
27 |
28 | // MARK: Helpers
29 |
30 | private func configureUI() {
31 | UIView.appearance().overrideUserInterfaceStyle = .light
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
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 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-29@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-29@3x.png",
25 | "scale" : "3x"
26 | },
27 | {
28 | "size" : "40x40",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-40@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-40@3x.png",
37 | "scale" : "3x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-60@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-60@3x.png",
49 | "scale" : "3x"
50 | },
51 | {
52 | "size" : "20x20",
53 | "idiom" : "ipad",
54 | "filename" : "Icon-20.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-20@2x-1.png",
61 | "scale" : "2x"
62 | },
63 | {
64 | "size" : "29x29",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-29.png",
67 | "scale" : "1x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-29@2x-1.png",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "40x40",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-40.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-40@2x-1.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "76x76",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-76.png",
91 | "scale" : "1x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-76@2x.png",
97 | "scale" : "2x"
98 | },
99 | {
100 | "size" : "83.5x83.5",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-83.5@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "1024x1024",
107 | "idiom" : "ios-marketing",
108 | "filename" : "iTunesArtwork@2x.png",
109 | "scale" : "1x"
110 | }
111 | ],
112 | "info" : {
113 | "version" : 1,
114 | "author" : "xcode"
115 | }
116 | }
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20@2x-1.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-20@3x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29@2x-1.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-76.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppLogo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iTunesArtwork@2x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/AppLogo.imageset/iTunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/XCoordinator-Example/910b4c624ab88b0a120bed13e5feace3c30eacd2/XCoordinator-Example/Common/Images.xcassets/AppLogo.imageset/iTunesArtwork@2x.png
--------------------------------------------------------------------------------
/XCoordinator-Example/Common/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/AboutCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by denis on 29/05/2019.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | enum AboutRoute: Route {
13 | case home
14 | case website
15 | }
16 |
17 | class AboutCoordinator: NavigationCoordinator {
18 |
19 | // MARK: Initialization
20 |
21 | init(rootViewController: UINavigationController) {
22 | super.init(rootViewController: rootViewController, initialRoute: nil)
23 | trigger(.home)
24 | }
25 |
26 | // MARK: Overrides
27 |
28 | override func prepareTransition(for route: AboutRoute) -> NavigationTransition {
29 | switch route {
30 | case .home:
31 | let viewController = AboutViewController()
32 | let viewModel = AboutViewModelImpl(router: unownedRouter)
33 | viewController.bind(to: viewModel)
34 | return .push(viewController)
35 | case .website:
36 | let url = URL(string: "https://quickbirdstudios.com/")!
37 | return Transition(presentables: [], animationInUse: nil) { _, _, completion in
38 | UIApplication.shared.open(url)
39 | completion?()
40 | }
41 | }
42 | }
43 |
44 | // MARK: Actions
45 |
46 | @objc
47 | private func openWebsite() {
48 | trigger(.website)
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/AppCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 03.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | enum AppRoute: Route {
13 | case login
14 | case home(StrongRouter?)
15 | case newsDetail(News)
16 | }
17 |
18 | class AppCoordinator: NavigationCoordinator {
19 |
20 | // MARK: Initialization
21 |
22 | init() {
23 | super.init(initialRoute: .login)
24 | }
25 |
26 | // MARK: Overrides
27 |
28 | override func prepareTransition(for route: AppRoute) -> NavigationTransition {
29 | switch route {
30 | case .login:
31 | let viewController = LoginViewController.instantiateFromNib()
32 | let viewModel = LoginViewModelImpl(router: unownedRouter)
33 | viewController.bind(to: viewModel)
34 | return .push(viewController)
35 | case let .home(router):
36 | if let router = router {
37 | return .presentFullScreen(router, animation: .fade)
38 | }
39 | let alert = UIAlertController(
40 | title: "How would you like to login?",
41 | message: "Please choose the type of coordinator used for the `Home` scene.",
42 | preferredStyle: .alert)
43 | alert.addAction(
44 | .init(title: "\(HomeTabCoordinator.self)", style: .default) { [unowned self] _ in
45 | self.trigger(.home(HomeTabCoordinator().strongRouter))
46 | }
47 | )
48 | alert.addAction(
49 | .init(title: "\(HomeSplitCoordinator.self)", style: .default) { [unowned self] _ in
50 | self.trigger(.home(HomeSplitCoordinator().strongRouter))
51 | }
52 | )
53 | alert.addAction(
54 | .init(title: "\(HomePageCoordinator.self)", style: .default) { [unowned self] _ in
55 | self.trigger(.home(HomePageCoordinator().strongRouter))
56 | }
57 | )
58 | alert.addAction(
59 | .init(title: "Random", style: .default) { [unowned self] _ in
60 | let routers: [() -> StrongRouter] = [
61 | { HomeTabCoordinator().strongRouter },
62 | { HomeSplitCoordinator().strongRouter },
63 | { HomeTabCoordinator().strongRouter }
64 | ]
65 | let router = routers.randomElement().map { $0() }
66 | self.trigger(.home(router))
67 | }
68 | )
69 | return .present(alert)
70 | case .newsDetail(let news):
71 | return .multiple(
72 | .dismissAll(),
73 | .popToRoot(),
74 | deepLink(AppRoute.home(HomePageCoordinator().strongRouter),
75 | HomeRoute.news,
76 | NewsRoute.newsDetail(news))
77 | )
78 | }
79 | }
80 |
81 | // MARK: Methods
82 |
83 | func notificationReceived() {
84 | guard let news = MockNewsService().mostRecentNews().articles.randomElement() else {
85 | return
86 | }
87 | self.trigger(.newsDetail(news))
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/HomePageCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomePageCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 30.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import XCoordinator
10 |
11 | class HomePageCoordinator: PageCoordinator {
12 |
13 | // MARK: Stored properties
14 |
15 | private let newsRouter: StrongRouter
16 | private let userListRouter: StrongRouter
17 |
18 | // MARK: Initialization
19 |
20 | init(newsRouter: StrongRouter = NewsCoordinator().strongRouter,
21 | userListRouter: StrongRouter = UserListCoordinator().strongRouter) {
22 | self.newsRouter = newsRouter
23 | self.userListRouter = userListRouter
24 |
25 | super.init(
26 | rootViewController: .init(transitionStyle: .scroll,
27 | navigationOrientation: .horizontal,
28 | options: nil),
29 | pages: [userListRouter, newsRouter], loop: false,
30 | set: userListRouter, direction: .forward
31 | )
32 | }
33 |
34 | // MARK: Overrides
35 |
36 | override func prepareTransition(for route: HomeRoute) -> PageTransition {
37 | switch route {
38 | case .news:
39 | return .set(newsRouter, direction: .forward)
40 | case .userList:
41 | return .set(userListRouter, direction: .reverse)
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/HomeSplitCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeSplitCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 30.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import XCoordinator
10 |
11 | class HomeSplitCoordinator: SplitCoordinator {
12 |
13 | // MARK: Stored properties
14 |
15 | private let newsRouter: StrongRouter
16 | private let userListRouter: StrongRouter
17 |
18 | // MARK: Initialization
19 |
20 | init(newsRouter: StrongRouter = NewsCoordinator().strongRouter,
21 | userListRouter: StrongRouter = UserListCoordinator().strongRouter) {
22 | self.newsRouter = newsRouter
23 | self.userListRouter = userListRouter
24 |
25 | super.init(master: userListRouter, detail: newsRouter)
26 | }
27 |
28 | // MARK: Overrides
29 |
30 | override func prepareTransition(for route: HomeRoute) -> SplitTransition {
31 | switch route {
32 | case .news:
33 | return .showDetail(newsRouter)
34 | case .userList:
35 | return .show(userListRouter)
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/HomeTabCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeTabCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 04.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | enum HomeRoute: Route {
13 | case news
14 | case userList
15 | }
16 |
17 | class HomeTabCoordinator: TabBarCoordinator {
18 |
19 | // MARK: Stored properties
20 |
21 | private let newsRouter: StrongRouter
22 | private let userListRouter: StrongRouter
23 |
24 | // MARK: Initialization
25 |
26 | convenience init() {
27 | let newsCoordinator = NewsCoordinator()
28 | newsCoordinator.rootViewController.tabBarItem = UITabBarItem(tabBarSystemItem: .recents, tag: 0)
29 |
30 | let userListCoordinator = UserListCoordinator()
31 | userListCoordinator.rootViewController.tabBarItem = UITabBarItem(tabBarSystemItem: .more, tag: 1)
32 |
33 | self.init(newsRouter: newsCoordinator.strongRouter,
34 | userListRouter: userListCoordinator.strongRouter)
35 | }
36 |
37 | init(newsRouter: StrongRouter,
38 | userListRouter: StrongRouter) {
39 | self.newsRouter = newsRouter
40 | self.userListRouter = userListRouter
41 |
42 | super.init(tabs: [newsRouter, userListRouter], select: userListRouter)
43 | }
44 |
45 | // MARK: Overrides
46 |
47 | override func prepareTransition(for route: HomeRoute) -> TabBarTransition {
48 | switch route {
49 | case .news:
50 | return .select(newsRouter)
51 | case .userList:
52 | return .select(userListRouter)
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/NewsCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import XCoordinator
10 |
11 | enum NewsRoute: Route {
12 | case news
13 | case newsDetail(News)
14 | case close
15 | }
16 |
17 | class NewsCoordinator: NavigationCoordinator {
18 |
19 | // MARK: Initialization
20 |
21 | init() {
22 | super.init(initialRoute: .news)
23 | }
24 |
25 | // MARK: Overrides
26 |
27 | override func prepareTransition(for route: NewsRoute) -> NavigationTransition {
28 | switch route {
29 | case .news:
30 | let viewController = NewsViewController.instantiateFromNib()
31 | let service = MockNewsService()
32 | let viewModel = NewsViewModelImpl(newsService: service, router: unownedRouter)
33 | viewController.bind(to: viewModel)
34 | return .push(viewController)
35 | case .newsDetail(let news):
36 | let viewController = NewsDetailViewController.instantiateFromNib()
37 | let viewModel = NewsDetailViewModelImpl(news: news)
38 | viewController.bind(to: viewModel)
39 | let animation: Animation
40 | if #available(iOS 10.0, *) {
41 | animation = .swirl
42 | } else {
43 | animation = .scale
44 | }
45 | return .push(viewController, animation: animation)
46 | case .close:
47 | return .dismissToRoot()
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/UserCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 09.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | enum UserRoute: Route {
13 | case user(String)
14 | case alert(title: String, message: String)
15 | case users
16 | case randomColor
17 | }
18 |
19 | class UserCoordinator: NavigationCoordinator {
20 |
21 | // MARK: Initialization
22 |
23 | init(user: String) {
24 | super.init(initialRoute: .user(user))
25 | }
26 |
27 | // MARK: Overrides
28 |
29 | override func prepareTransition(for route: UserRoute) -> NavigationTransition {
30 | switch route {
31 | case .randomColor:
32 | let viewController = UIViewController()
33 | viewController.view.backgroundColor = .random()
34 | return .push(viewController, animation: .fade)
35 | case let .user(username):
36 | let viewController = UserViewController.instantiateFromNib()
37 | let viewModel = UserViewModelImpl(router: unownedRouter, username: username)
38 | viewController.bind(to: viewModel)
39 | return .push(viewController)
40 | case let .alert(title, message):
41 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
42 | alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
43 | return .present(alert)
44 | case .users:
45 | return .dismiss()
46 | }
47 | }
48 |
49 | override func presented(from presentable: Presentable?) {
50 | super.presented(from: presentable)
51 | addPushGestureRecognizer(to: rootViewController)
52 | }
53 |
54 | // MARK: Helpers
55 |
56 | private func addPushGestureRecognizer(to container: Container) {
57 | let view = container.view
58 | let gestureRecognizer = UIScreenEdgePanGestureRecognizer()
59 | gestureRecognizer.edges = .right
60 | view?.addGestureRecognizer(gestureRecognizer)
61 |
62 | registerInteractiveTransition(
63 | for: .randomColor,
64 | triggeredBy: gestureRecognizer,
65 | progress: { [weak view] recognizer in
66 | let xTranslation = -recognizer.translation(in: view).x
67 | return max(min(xTranslation / UIScreen.main.bounds.width, 1), 0)
68 | },
69 | shouldFinish: { [weak view] recognizer in
70 | let xTranslation = -recognizer.translation(in: view).x
71 | let xVelocity = -recognizer.velocity(in: view).x
72 | return xTranslation >= UIScreen.main.bounds.width / 2
73 | || xVelocity >= UIScreen.main.bounds.width / 2
74 | },
75 | completion: nil
76 | )
77 | }
78 |
79 | }
80 |
81 | // MARK: - Private extensions
82 |
83 | extension UIColor {
84 |
85 | fileprivate static func random(alpha: CGFloat? = 1) -> UIColor {
86 | return UIColor(
87 | red: CGFloat.random(in: 0...1),
88 | green: CGFloat.random(in: 0...1),
89 | blue: CGFloat.random(in: 0...1),
90 | alpha: alpha ?? CGFloat.random(in: 0...1)
91 | )
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Coordinators/UserListCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserListCoordinator.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import XCoordinator
10 |
11 | enum UserListRoute: Route {
12 | case home
13 | case users
14 | case user(String)
15 | case registerUsersPeek(from: Container)
16 | case logout
17 | case about
18 | }
19 |
20 | class UserListCoordinator: NavigationCoordinator {
21 |
22 | // MARK: Initialization
23 |
24 | init() {
25 | super.init(initialRoute: .home)
26 | }
27 |
28 | // MARK: Overrides
29 |
30 | override func prepareTransition(for route: UserListRoute) -> NavigationTransition {
31 | switch route {
32 | case .home:
33 | let viewController = HomeViewController.instantiateFromNib()
34 | let viewModel = HomeViewModelImpl(router: unownedRouter)
35 | viewController.bind(to: viewModel)
36 | return .push(viewController)
37 | case .users:
38 | let viewController = UsersViewController.instantiateFromNib()
39 | let viewModel = UsersViewModelImpl(userService: MockUserService(), router: unownedRouter)
40 | viewController.bind(to: viewModel)
41 | return .push(viewController, animation: .fade)
42 | case .user(let username):
43 | let coordinator = UserCoordinator(user: username)
44 | return .present(coordinator, animation: .default)
45 | case .registerUsersPeek(let source):
46 | if #available(iOS 13.0, *) {
47 | return .none()
48 | } else {
49 | return registerPeek(for: source, route: .users)
50 | }
51 | case .logout:
52 | return .dismiss()
53 | case .about:
54 | addChild(AboutCoordinator(rootViewController: rootViewController))
55 | return .none()
56 | }
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Extensions/CGAffineTransform+InPlace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGAffineTransform+InPlace.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.12.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension CGAffineTransform {
12 |
13 | mutating func rotate(by rotationAngle: CGFloat) {
14 | self = self.rotated(by: rotationAngle)
15 | }
16 |
17 | mutating func scale(by scalingFactor: CGFloat) {
18 | self = self.scaledBy(x: scalingFactor, y: scalingFactor)
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Extensions/Presentable+Rx.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Presentable+Rx.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 15.12.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | #if canImport(XCoordinator) && canImport(UIKit) && canImport(RxSwift) && canImport(RxCocoa)
10 |
11 | import RxCocoa
12 | import RxSwift
13 | import UIKit
14 | import XCoordinator
15 |
16 | extension Reactive where Base: Presentable {
17 |
18 | public var dismissal: Observable! {
19 | guard let viewController = base.viewController else {
20 | return nil
21 | }
22 | return viewController.rx
23 | .methodInvoked(#selector(UIViewController.viewDidDisappear(_:)))
24 | .map { _ in }
25 | .filter { [weak viewController] in
26 | guard let viewController = viewController else { return false }
27 | return viewController.isBeingDismissed || viewController.isMovingFromParent
28 | }
29 | }
30 |
31 | }
32 |
33 | #endif
34 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Extensions/TransitionAnimation+Defaults.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionAnimation+Defaults.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.12.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | let defaultAnimationDuration: TimeInterval = 0.35
13 |
14 | extension CGFloat {
15 | static let verySmall: CGFloat = 0.0001
16 | }
17 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Extensions/Transitions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Transitions.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 17.09.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 |
12 | extension Transition {
13 |
14 | static func presentFullScreen(_ presentable: Presentable, animation: Animation? = nil) -> Transition {
15 | presentable.viewController?.modalPresentationStyle = .fullScreen
16 | return .present(presentable, animation: animation)
17 | }
18 |
19 | static func dismissAll() -> Transition {
20 | return Transition(presentables: [], animationInUse: nil) { rootViewController, options, completion in
21 | guard let presentedViewController = rootViewController.presentedViewController else {
22 | completion?()
23 | return
24 | }
25 | presentedViewController.dismiss(animated: options.animated) {
26 | Transition.dismissAll()
27 | .perform(on: rootViewController, with: options, completion: completion)
28 | }
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Models/News.swift:
--------------------------------------------------------------------------------
1 | //
2 | // News.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 17.09.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import class UIKit.UIImage
11 |
12 | struct News {
13 | var title: String
14 | var subtitle: String
15 | var image: UIImage
16 | var content: String
17 | }
18 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Models/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 17.09.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct User {
12 | var name: String
13 | }
14 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/About/AboutViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutViewController.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 17.09.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 | import RxCocoa
12 | import WebKit
13 |
14 | class AboutViewController: UIViewController, BindableType {
15 |
16 | var viewModel: AboutViewModel!
17 |
18 | // MARK: Views
19 |
20 | private let webView: WKWebView = {
21 | let view = WKWebView()
22 | view.translatesAutoresizingMaskIntoConstraints = false
23 | return view
24 | }()
25 |
26 | private lazy var openWebsiteButton =
27 | UIBarButtonItem(title: "Website", style: .done,
28 | target: self, action: #selector(openWebsite))
29 |
30 | // MARK: Stored properties
31 |
32 | private let disposeBag = DisposeBag()
33 |
34 | // MARK: Overrides
35 |
36 | override func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | title = "XCoordinator"
40 | navigationItem.rightBarButtonItem = openWebsiteButton
41 | setupWebView()
42 | }
43 |
44 | // MARK: BindableType
45 |
46 | func bindViewModel() {
47 | let request = URLRequest(url: viewModel.output.url)
48 | webView.load(request)
49 | }
50 |
51 | // MARK: Actions
52 |
53 | @objc
54 | private func openWebsite() {
55 | viewModel.input.openWebsiteTrigger.onNext(())
56 | }
57 |
58 | // MARK: Helpers
59 |
60 | private func setupWebView() {
61 | view.addSubview(webView)
62 |
63 | NSLayoutConstraint.activate([
64 | webView.topAnchor
65 | .constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
66 | webView.bottomAnchor
67 | .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
68 | webView.leadingAnchor
69 | .constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
70 | webView.trailingAnchor
71 | .constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
72 | ])
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/About/AboutViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutViewModel.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 17.09.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import Action
12 |
13 | protocol AboutViewModelInput {
14 | var openWebsiteTrigger: AnyObserver { get }
15 | }
16 |
17 | protocol AboutViewModelOutput {
18 | var url: URL { get }
19 | }
20 |
21 | protocol AboutViewModel {
22 | var input: AboutViewModelInput { get }
23 | var output: AboutViewModelOutput { get }
24 | }
25 |
26 | extension AboutViewModel where Self: AboutViewModelInput & AboutViewModelOutput {
27 | var input: AboutViewModelInput { return self }
28 | var output: AboutViewModelOutput { return self }
29 | }
30 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/About/AboutViewModelImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutViewModelImpl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 17.09.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import Action
12 | import XCoordinator
13 |
14 | class AboutViewModelImpl: AboutViewModel, AboutViewModelInput, AboutViewModelOutput {
15 |
16 | // MARK: Inputs
17 |
18 | private(set) lazy var openWebsiteTrigger = openWebsiteAction.inputs
19 |
20 | // MARK: Actions
21 |
22 | private lazy var openWebsiteAction = CocoaAction { [unowned self] in
23 | self.router.rx.trigger(.website)
24 | }
25 |
26 | // MARK: Outputs
27 |
28 | let url = URL(string: "https://github.com/quickbirdstudios/XCoordinator")!
29 |
30 | // MARK: Stored properties
31 |
32 | private let router: UnownedRouter
33 |
34 | // MARK: Initialization
35 |
36 | init(router: UnownedRouter) {
37 | self.router = router
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Home/HomeViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeViewController.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 04.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import RxCocoa
10 | import RxSwift
11 | import UIKit
12 |
13 | class HomeViewController: UIViewController, BindableType {
14 | var viewModel: HomeViewModel!
15 |
16 | // MARK: Views
17 |
18 | @IBOutlet private var logoutButton: UIButton!
19 | @IBOutlet private var usersButton: UIButton!
20 | @IBOutlet private var aboutButton: UIButton!
21 |
22 | // MARK: Stored properties
23 |
24 | private let disposeBag = DisposeBag()
25 |
26 | // MARK: Overrides
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 |
31 | title = "Home"
32 | }
33 |
34 | // MARK: BindableType
35 |
36 | func bindViewModel() {
37 | logoutButton.rx.tap
38 | .bind(to: viewModel.input.logoutTrigger)
39 | .disposed(by: disposeBag)
40 |
41 | usersButton.rx.tap
42 | .bind(to: viewModel.input.usersTrigger)
43 | .disposed(by: disposeBag)
44 |
45 | aboutButton.rx.tap
46 | .bind(to: viewModel.input.aboutTrigger)
47 | .disposed(by: disposeBag)
48 |
49 | viewModel.registerPeek(for: usersButton)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Home/HomeViewController.xib:
--------------------------------------------------------------------------------
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 |
38 |
48 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Home/HomeViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeViewModel.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 04.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | protocol HomeViewModelInput {
14 | var logoutTrigger: AnyObserver { get }
15 | var usersTrigger: AnyObserver { get }
16 | var aboutTrigger: AnyObserver { get }
17 | }
18 |
19 | protocol HomeViewModelOutput {}
20 |
21 | protocol HomeViewModel {
22 | var input: HomeViewModelInput { get }
23 | var output: HomeViewModelOutput { get }
24 |
25 | func registerPeek(for sourceView: Container)
26 | }
27 |
28 | extension HomeViewModel where Self: HomeViewModelInput & HomeViewModelOutput {
29 | var input: HomeViewModelInput { return self }
30 | var output: HomeViewModelOutput { return self }
31 | }
32 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Home/HomeViewModelImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeViewModelImpl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 20.11.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 | import XCoordinatorRx
13 |
14 | class HomeViewModelImpl: HomeViewModel, HomeViewModelInput, HomeViewModelOutput {
15 |
16 | // MARK: Inputs
17 |
18 | private(set) lazy var logoutTrigger = logoutAction.inputs
19 | private(set) lazy var usersTrigger = usersAction.inputs
20 | private(set) lazy var aboutTrigger = aboutAction.inputs
21 |
22 | // MARK: Actions
23 |
24 | private lazy var logoutAction = CocoaAction { [unowned self] in
25 | self.router.rx.trigger(.logout)
26 | }
27 |
28 | private lazy var usersAction = CocoaAction { [unowned self] in
29 | self.router.rx.trigger(.users)
30 | }
31 |
32 | private lazy var aboutAction = CocoaAction { [unowned self] in
33 | self.router.rx.trigger(.about)
34 | }
35 | // MARK: Stored properties
36 |
37 | private let router: UnownedRouter
38 |
39 | // MARK: Initialization
40 |
41 | init(router: UnownedRouter) {
42 | self.router = router
43 | }
44 |
45 | // MARK: Methods
46 |
47 | func registerPeek(for sourceView: Container) {
48 | router.trigger(.registerUsersPeek(from: sourceView))
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Login/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewController.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 03.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import RxCocoa
10 | import RxSwift
11 | import UIKit
12 |
13 | class LoginViewController: UIViewController, BindableType {
14 | var viewModel: LoginViewModel!
15 |
16 | // MARK: Views
17 |
18 | @IBOutlet private var loginButton: UIButton!
19 |
20 | // MARK: Stored properties
21 |
22 | private let disposeBag = DisposeBag()
23 |
24 | // MARK: Overrides
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 |
29 | title = "Login"
30 | }
31 |
32 | // MARK: BindableType
33 |
34 | func bindViewModel() {
35 | loginButton.rx.tap
36 | .bind(to: viewModel.input.loginTrigger)
37 | .disposed(by: disposeBag)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Login/LoginViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Login/LoginViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewModel.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 03.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | protocol LoginViewModelInput {
14 | var loginTrigger: AnyObserver { get }
15 | }
16 |
17 | protocol LoginViewModelOutput {}
18 |
19 | protocol LoginViewModel {
20 | var input: LoginViewModelInput { get }
21 | var output: LoginViewModelOutput { get }
22 | }
23 |
24 | extension LoginViewModel where Self: LoginViewModelInput & LoginViewModelOutput {
25 | var input: LoginViewModelInput { return self }
26 | var output: LoginViewModelOutput { return self }
27 | }
28 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/Login/LoginViewModelImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewModelImpl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 20.11.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | class LoginViewModelImpl: LoginViewModel, LoginViewModelInput, LoginViewModelOutput {
14 |
15 | // MARK: Inputs
16 |
17 | private(set) lazy var loginTrigger = loginAction.inputs
18 |
19 | // MARK: Actions
20 |
21 | private lazy var loginAction = CocoaAction { [unowned self] in
22 | self.router.rx.trigger(.home(nil))
23 | }
24 |
25 | // MARK: Stored properties
26 |
27 | private let router: UnownedRouter
28 |
29 | // MARK: Initialization
30 |
31 | init(router: UnownedRouter) {
32 | self.router = router
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/News/NewsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsViewController.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import RxCocoa
10 | import RxSwift
11 | import UIKit
12 |
13 | class NewsViewController: UIViewController, BindableType {
14 | var viewModel: NewsViewModel!
15 |
16 | // MARK: Views
17 |
18 | @IBOutlet private var tableView: UITableView!
19 |
20 | // MARK: Stored properties
21 |
22 | private let disposeBag = DisposeBag()
23 | private let tableViewCellIdentifier = String(describing: DetailTableViewCell.self)
24 |
25 | // MARK: Overrides
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 |
30 | tableView.register(DetailTableViewCell.self, forCellReuseIdentifier: tableViewCellIdentifier)
31 | tableView.rowHeight = 44
32 | }
33 |
34 | // MARK: BindableType
35 |
36 | func bindViewModel() {
37 | viewModel.output.news
38 | .bind(to: tableView.rx.items(cellIdentifier: tableViewCellIdentifier)) { _, model, cell in
39 | cell.textLabel?.text = model.title
40 | cell.detailTextLabel?.text = model.subtitle
41 | cell.imageView?.image = model.image
42 | cell.selectionStyle = .none
43 | }
44 | .disposed(by: disposeBag)
45 |
46 | tableView.rx.modelSelected(News.self)
47 | .bind(to: viewModel.input.selectedNews)
48 | .disposed(by: disposeBag)
49 |
50 | viewModel.output.title
51 | .bind(to: navigationItem.rx.title)
52 | .disposed(by: disposeBag)
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/News/NewsViewController.xib:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/News/NewsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsViewModel.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 |
12 | protocol NewsViewModelInput {
13 | var selectedNews: AnyObserver { get }
14 | }
15 |
16 | protocol NewsViewModelOutput {
17 | var news: Observable<[News]> { get }
18 | var title: Observable { get }
19 | }
20 |
21 | protocol NewsViewModel {
22 | var input: NewsViewModelInput { get }
23 | var output: NewsViewModelOutput { get }
24 | }
25 |
26 | extension NewsViewModel where Self: NewsViewModelInput & NewsViewModelOutput {
27 | var input: NewsViewModelInput { return self }
28 | var output: NewsViewModelOutput { return self }
29 | }
30 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/News/NewsViewModelImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsViewModelImpl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | class NewsViewModelImpl: NewsViewModel, NewsViewModelInput, NewsViewModelOutput {
14 |
15 | // MARK: Inputs
16 |
17 | private(set) lazy var selectedNews = newsSelectedAction.inputs
18 |
19 | // MARK: Actions
20 |
21 | lazy var newsSelectedAction = Action { [unowned self] news in
22 | self.router.rx.trigger(.newsDetail(news))
23 | }
24 |
25 | // MARK: Outputs
26 |
27 | private(set) lazy var news = newsObservable.map { $0.articles }
28 | private(set) lazy var title = newsObservable.map { $0.title }
29 |
30 | let newsObservable: Observable<(title: String, articles: [News])>
31 |
32 | // MARK: Stored properties
33 |
34 | private let newsService: NewsService
35 | private let router: UnownedRouter
36 |
37 | // MARK: Initialization
38 |
39 | init(newsService: NewsService, router: UnownedRouter) {
40 | self.newsService = newsService
41 | self.newsObservable = .just(newsService.mostRecentNews())
42 | self.router = router
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsDetailViewController.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import RxCocoa
10 | import RxSwift
11 | import UIKit
12 |
13 | class NewsDetailViewController: UIViewController, BindableType {
14 | var viewModel: NewsDetailViewModel!
15 |
16 | // MARK: Views
17 |
18 | @IBOutlet private var imageView: UIImageView!
19 | @IBOutlet private var titleLabel: UILabel!
20 | @IBOutlet private var contentTextView: UITextView!
21 |
22 | // MARK: Stored properties
23 |
24 | private let disposeBag = DisposeBag()
25 |
26 | // MARK: Overrides
27 |
28 | override func viewDidLayoutSubviews() {
29 | super.viewDidLayoutSubviews()
30 |
31 | contentTextView.setContentOffset(.zero, animated: false)
32 | }
33 |
34 | // MARK: BindableType
35 |
36 | func bindViewModel() {
37 | viewModel.output.news
38 | .map { $0.title + "\n" + $0.subtitle }
39 | .bind(to: titleLabel.rx.text)
40 | .disposed(by: disposeBag)
41 |
42 | viewModel.output.news
43 | .map { $0.content }
44 | .bind(to: contentTextView.rx.text)
45 | .disposed(by: disposeBag)
46 |
47 | viewModel.output.news
48 | .map { $0.image }
49 | .bind(to: imageView.rx.image)
50 | .disposed(by: disposeBag)
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewController.xib:
--------------------------------------------------------------------------------
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 |
39 |
40 |
41 |
42 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsDetailViewModel.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 |
12 | protocol NewsDetailViewModelInput {
13 | }
14 |
15 | protocol NewsDetailViewModelOutput {
16 | var news: Observable { get }
17 | }
18 |
19 | protocol NewsDetailViewModel {
20 | var input: NewsDetailViewModelInput { get }
21 | var output: NewsDetailViewModelOutput { get }
22 | }
23 |
24 | extension NewsDetailViewModel where Self: NewsDetailViewModelInput & NewsDetailViewModelOutput {
25 | var input: NewsDetailViewModelInput { return self }
26 | var output: NewsDetailViewModelOutput { return self }
27 | }
28 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModelImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsDetailViewModelImpl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import Foundation
11 | import RxSwift
12 |
13 | class NewsDetailViewModelImpl: NewsDetailViewModel, NewsDetailViewModelInput, NewsDetailViewModelOutput {
14 |
15 | // MARK: Outputs
16 |
17 | let news: Observable
18 |
19 | // MARK: Initialization
20 |
21 | init(news: News) {
22 | self.news = .just(news)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/User/UserViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserViewController.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 09.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import RxCocoa
10 | import RxSwift
11 | import UIKit
12 |
13 | class UserViewController: UIViewController, BindableType {
14 | var viewModel: UserViewModel!
15 |
16 | // MARK: Views
17 |
18 | @IBOutlet private var username: UILabel!
19 | @IBOutlet private var showAlertButton: UIButton!
20 | private var closeBarButtonItem: UIBarButtonItem!
21 |
22 | // MARK: Stored properties
23 |
24 | private let disposeBag = DisposeBag()
25 |
26 | // MARK: Initialization
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 |
31 | configureNavigationBar()
32 | }
33 |
34 | // MARK: BindableType
35 |
36 | func bindViewModel() {
37 | viewModel.output.username
38 | .bind(to: username.rx.text)
39 | .disposed(by: disposeBag)
40 |
41 | showAlertButton.rx.tap
42 | .bind(to: viewModel.input.alertTrigger)
43 | .disposed(by: disposeBag)
44 |
45 | closeBarButtonItem.rx.tap
46 | .bind(to: viewModel.input.closeTrigger)
47 | .disposed(by: disposeBag)
48 | }
49 |
50 | // MARK: Helpers
51 |
52 | private func configureNavigationBar() {
53 | closeBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: nil)
54 | navigationItem.leftBarButtonItem = closeBarButtonItem
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/User/UserViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/User/UserViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserViewModel.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 09.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | protocol UserViewModelInput {
14 | var alertTrigger: AnyObserver { get }
15 | var closeTrigger: AnyObserver { get }
16 | }
17 |
18 | protocol UserViewModelOutput {
19 | var username: Observable { get }
20 | }
21 |
22 | protocol UserViewModel {
23 | var input: UserViewModelInput { get }
24 | var output: UserViewModelOutput { get }
25 | }
26 |
27 | extension UserViewModel where Self: UserViewModelInput & UserViewModelOutput {
28 | var input: UserViewModelInput { return self }
29 | var output: UserViewModelOutput { return self }
30 | }
31 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/User/UserViewModelImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserViewModelImpl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 20.11.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | class UserViewModelImpl: UserViewModel, UserViewModelInput, UserViewModelOutput {
14 |
15 | // MARK: Inputs
16 |
17 | private(set) lazy var alertTrigger = alertAction.inputs
18 | private(set) lazy var closeTrigger = closeAction.inputs
19 |
20 | // MARK: Actions
21 |
22 | private lazy var alertAction = CocoaAction { [unowned self] in
23 | self.router.rx.trigger(.alert(title: "Hey", message: "You are awesome!"))
24 | }
25 |
26 | private lazy var closeAction = CocoaAction { [unowned self] in
27 | self.router.rx.trigger(.users)
28 | }
29 |
30 | // MARK: Outputs
31 |
32 | let username: Observable
33 |
34 | // MARK: Stored properties
35 |
36 | private let router: UnownedRouter
37 |
38 | // MARK: Initialization
39 |
40 | init(router: UnownedRouter, username: String) {
41 | self.router = router
42 | self.username = .just(username)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/UserList/UsersViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersViewController.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 04.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import RxCocoa
10 | import RxSwift
11 | import UIKit
12 |
13 | class UsersViewController: UIViewController, BindableType {
14 | var viewModel: UsersViewModel!
15 |
16 | // MARK: Views
17 |
18 | @IBOutlet private var tableView: UITableView!
19 |
20 | // MARK: Stored properties
21 |
22 | private let disposeBag = DisposeBag()
23 | private let cellIdentifier = String(describing: DetailTableViewCell.self)
24 |
25 | // MARK: Initialization
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 |
30 | configureTableViewCell()
31 | configureNavigationBar()
32 | }
33 |
34 | // MARK: BindableType
35 |
36 | func bindViewModel() {
37 | viewModel.output.users
38 | .bind(to: tableView.rx.items(cellIdentifier: cellIdentifier)) { _, element, cell in
39 | cell.textLabel?.text = element.name
40 | cell.selectionStyle = .none
41 | }
42 | .disposed(by: disposeBag)
43 |
44 | tableView.rx.modelSelected(User.self)
45 | .bind(to: viewModel.input.showUserTrigger)
46 | .disposed(by: disposeBag)
47 | }
48 |
49 | // MARK: Helpers
50 |
51 | private func configureTableViewCell() {
52 | tableView.register(DetailTableViewCell.self, forCellReuseIdentifier: cellIdentifier)
53 | }
54 |
55 | private func configureNavigationBar() {
56 | title = "Users"
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/UserList/UsersViewController.xib:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/UserList/UsersViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersViewModel.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 04.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | protocol UsersViewModelInput {
14 | var showUserTrigger: AnyObserver { get }
15 | }
16 |
17 | protocol UsersViewModelOutput {
18 | var users: Observable<[User]> { get }
19 | }
20 |
21 | protocol UsersViewModel {
22 | var input: UsersViewModelInput { get }
23 | var output: UsersViewModelOutput { get }
24 | }
25 |
26 | extension UsersViewModel where Self: UsersViewModelInput & UsersViewModelOutput {
27 | var input: UsersViewModelInput { return self }
28 | var output: UsersViewModelOutput { return self }
29 | }
30 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Scenes/UserList/UsersViewModelImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsersViewModelImpl.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 20.11.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Action
10 | import RxSwift
11 | import XCoordinator
12 |
13 | class UsersViewModelImpl: UsersViewModel, UsersViewModelInput, UsersViewModelOutput {
14 |
15 | // MARK: Inputs
16 |
17 | private(set) lazy var showUserTrigger = showUserAction.inputs
18 |
19 | // MARK: Actions
20 |
21 | private lazy var showUserAction = Action { [unowned self] user in
22 | self.router.rx.trigger(.user(user.name))
23 | }
24 |
25 | // MARK: Outputs
26 |
27 | private(set) lazy var users = Observable.just(userService.allUsers())
28 |
29 | // MARK: Stored properties
30 |
31 | private let userService: UserService
32 | private let router: UnownedRouter
33 |
34 | // MARK: Initialization
35 |
36 | init(userService: UserService, router: UnownedRouter) {
37 | self.userService = userService
38 | self.router = router
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Services/NewsService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsService.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 28.07.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 |
12 | // swiftlint:disable line_length
13 |
14 | protocol NewsService {
15 | func mostRecentNews() -> (title: String, articles: [News])
16 | }
17 |
18 | class MockNewsService: NewsService {
19 |
20 | private let userService = MockUserService()
21 |
22 | private let images = [UIColor.black, .blue, .green, .yellow, .orange, .red, .white]
23 | .map { UIImage.color($0, size: CGSize(width: 44, height: 44))! }
24 |
25 | func mostRecentNews() -> (title: String, articles: [News]) {
26 | let articles = userService.allUsers().enumerated().map { index, user -> News in
27 | News(title: "Article \(index)",
28 | subtitle: user.name,
29 | image: images[index % images.count],
30 | content: loremIpsum)
31 | }
32 | return (title: "QuickBird Studios Blog", articles: articles)
33 | }
34 |
35 | }
36 |
37 | extension UIImage {
38 |
39 | static func color(_ color: UIColor, size: CGSize = .init(width: 1, height: 1)) -> UIImage! {
40 | let rect = CGRect(origin: .zero, size: size)
41 | UIGraphicsBeginImageContext(rect.size)
42 | guard let context = UIGraphicsGetCurrentContext() else { return nil }
43 | defer { UIGraphicsEndImageContext() }
44 | context.setFillColor(color.cgColor)
45 | context.fill(rect)
46 | return UIGraphicsGetImageFromCurrentImageContext()
47 | }
48 |
49 | }
50 |
51 | let loremIpsum = """
52 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus iaculis, augue ac consectetur volutpat, dui est malesuada tellus, et elementum odio urna quis odio. Mauris mollis at libero in elementum. Mauris enim dui, tincidunt id blandit vitae, condimentum a tellus. Donec ut diam in nisl interdum ultrices. Vivamus id magna nisi. Duis molestie libero velit, vel consequat mi viverra pellentesque. Nulla at tellus eget risus fringilla ornare id a quam. Pellentesque arcu neque, interdum nec enim eu, tincidunt volutpat mauris. Vivamus ultricies tortor at lacus vehicula, vitae laoreet tellus tincidunt. Etiam sollicitudin nisl scelerisque odio malesuada consectetur. Nunc non tempor felis. Sed dolor ipsum, scelerisque vitae dolor in, porttitor facilisis lorem. Nam id dolor sagittis, fermentum nulla eu, ornare neque.
53 |
54 | Integer a dignissim nulla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ultricies velit ipsum, vel varius lorem luctus nec. Fusce egestas pretium magna, in convallis turpis varius in. Phasellus fringilla magna tincidunt, tempor nulla in, ullamcorper enim. Proin pellentesque mi congue ante tempor, eget consectetur nunc eleifend. Cras posuere dui dui, sit amet sodales sapien aliquam eu. Morbi non neque quam. Vivamus et maximus nisl. Donec quis lectus nunc. Sed eget nulla quis enim gravida dapibus. Sed eu nisl et erat dictum rutrum at vitae neque. Quisque vel nunc viverra, malesuada nulla ut, mollis dui. Aliquam feugiat quam at magna dapibus ultrices. Vestibulum viverra orci et tellus vehicula, vel congue neque ornare.
55 |
56 | Suspendisse potenti. Ut sed felis consequat, pharetra tellus sed, placerat dui. In aliquet nunc eu semper suscipit. In eros odio, ornare et blandit non, volutpat lobortis ex. Ut condimentum enim sit amet blandit tincidunt. Phasellus et elit dui. Vivamus condimentum id lorem a tincidunt. Donec semper turpis vel condimentum ullamcorper. Vivamus vulputate, tellus id facilisis iaculis, dui diam congue diam, sit amet fringilla orci diam in ex. Donec elit enim, consequat id sodales sed, blandit id justo. In hac habitasse platea dictumst. Donec justo lectus, sagittis quis iaculis vel, tempor non elit. Cras tellus ipsum, vulputate quis nisl ut, lobortis venenatis orci.
57 |
58 | Ut vitae ex eget quam accumsan laoreet sit amet a ligula. Quisque eu nisi nec leo aliquam faucibus id commodo tortor. Aliquam lobortis felis in ipsum placerat, aliquet condimentum tortor malesuada. In vitae enim ut orci imperdiet tempor. Vivamus nec nisi ut quam tempus volutpat a vitae eros. Etiam interdum facilisis rhoncus. Vestibulum tempus pulvinar velit, nec luctus dolor faucibus vel. Sed ullamcorper maximus odio, non mattis orci eleifend efficitur.
59 |
60 | Sed bibendum, mi sed euismod gravida, nulla elit aliquam augue, eget fermentum ligula urna eu lorem. Ut sit amet sem diam. Sed ac blandit tellus, ut rhoncus orci. Nulla non dui at nisl interdum venenatis non eget tellus. Pellentesque congue felis ut posuere mollis. Cras sit amet rutrum neque. Morbi id porttitor metus.
61 | """
62 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Services/UserService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserService.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 17.09.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | protocol UserService {
10 | func allUsers() -> [User]
11 | }
12 |
13 | class MockUserService: UserService {
14 | func allUsers() -> [User] {
15 | let names = [
16 | "Stefan", "Malte", "Sebi",
17 | "Niko", "Balazs", "Patrick",
18 | "Julian", "Quirin", "Paul",
19 | "Michael", "Eduardo", "Lizzie"
20 | ]
21 | return names.map(User.init)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Utils/BindableType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BindableType.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 03.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | protocol BindableType: AnyObject {
13 | associatedtype ViewModelType
14 |
15 | var viewModel: ViewModelType! { get set }
16 |
17 | func bindViewModel()
18 | }
19 |
20 | extension BindableType where Self: UIViewController {
21 | func bind(to model: Self.ViewModelType) {
22 | viewModel = model
23 | loadViewIfNeeded()
24 | bindViewModel()
25 | }
26 | }
27 |
28 | extension BindableType where Self: UITableViewCell {
29 | func bind(to model: Self.ViewModelType) {
30 | viewModel = model
31 | bindViewModel()
32 | }
33 | }
34 |
35 | extension BindableType where Self: UICollectionViewCell {
36 | func bind(to model: Self.ViewModelType) {
37 | viewModel = model
38 | bindViewModel()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Utils/NibIdentifiable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NibIdentifiable.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Joan Disho on 03.05.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | protocol NibIdentifiable {
13 | static var nibIdentifier: String { get }
14 | }
15 |
16 | extension NibIdentifiable {
17 | static var nib: UINib {
18 | return UINib(nibName: nibIdentifier, bundle: nil)
19 | }
20 | }
21 |
22 | extension UIView: NibIdentifiable {
23 | static var nibIdentifier: String {
24 | return String(describing: self)
25 | }
26 | }
27 |
28 | extension UIViewController: NibIdentifiable {
29 | static var nibIdentifier: String {
30 | return String(describing: self)
31 | }
32 | }
33 |
34 | extension NibIdentifiable where Self: UIViewController {
35 |
36 | static func instantiateFromNib() -> Self {
37 | return Self(nibName: nibIdentifier, bundle: nil)
38 | }
39 |
40 | }
41 |
42 | extension NibIdentifiable where Self: UIView {
43 |
44 | static func instantiateFromNib() -> Self {
45 | guard let view = UINib(nibName: nibIdentifier, bundle: nil)
46 | .instantiate(withOwner: nil, options: nil).first as? Self else {
47 | fatalError("Couldn't find nib file for \(String(describing: Self.self))")
48 | }
49 | return view
50 | }
51 |
52 | }
53 |
54 | extension UITableView {
55 |
56 | func registerCell(type: T.Type) {
57 | register(type.nib, forCellReuseIdentifier: String(describing: T.self))
58 | }
59 |
60 | func registerHeaderFooterView(type: T.Type) {
61 | register(type.nib, forHeaderFooterViewReuseIdentifier: String(describing: T.self))
62 | }
63 |
64 | func dequeueReusableCell(type: T.Type) -> T {
65 | guard let cell = self.dequeueReusableCell(withIdentifier: String(describing: T.self)) as? T else {
66 | fatalError("Couldn't find nib file for \(String(describing: T.self))")
67 | }
68 | return cell
69 | }
70 |
71 | func dequeueReusableCell(type: T.Type, forIndexPath indexPath: IndexPath) -> T {
72 | guard let cell = self.dequeueReusableCell(withIdentifier: String(describing: T.self),
73 | for: indexPath) as? T else {
74 | fatalError("Couldn't find nib file for \(String(describing: T.self))")
75 | }
76 | return cell
77 | }
78 |
79 | func dequeueReusableHeaderFooterView(type: T.Type) -> T {
80 | guard let headerFooterView = self
81 | .dequeueReusableHeaderFooterView(withIdentifier: String(describing: T.self)) as? T else {
82 | fatalError("Couldn't find nib file for \(String(describing: T.self))")
83 | }
84 | return headerFooterView
85 | }
86 |
87 | }
88 |
89 | extension UICollectionView {
90 |
91 | func registerCell(type: T.Type) {
92 | register(type.nib, forCellWithReuseIdentifier: String(describing: T.self))
93 | }
94 |
95 | func dequeueReusableCell(type: T.Type, forIndexPath indexPath: IndexPath) -> T {
96 | guard let cell = self.dequeueReusableCell(withReuseIdentifier: String(describing: T.self),
97 | for: indexPath) as? T else {
98 | fatalError("Couldn't find nib file for \(String(describing: T.self))")
99 | }
100 | return cell
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/XCoordinator-Example/Views/DetailTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailTableViewCell.swift
3 | // XCoordinator-Example
4 | //
5 | // Created by Paul Kraft on 20.11.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class DetailTableViewCell: UITableViewCell {
12 |
13 | // MARK: Initialization
14 |
15 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
16 | super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
17 | }
18 |
19 | required init?(coder aDecoder: NSCoder) {
20 | super.init(coder: aDecoder)
21 | }
22 |
23 | // MARK: Overrides
24 |
25 | override func setHighlighted(_ highlighted: Bool, animated: Bool) {
26 | super.setHighlighted(highlighted, animated: animated)
27 |
28 | UIView.animate(withDuration: animated ? 0.35 : 0) {
29 | self.backgroundColor = highlighted ? .lightGray : .white
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/AnimationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimationTests.swift
3 | // XCoordinator_Example
4 | //
5 | // Created by Paul Kraft on 16.09.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 | import XCTest
12 |
13 | class AnimationTests: XCTestCase {
14 |
15 | // MARK: Static properties
16 |
17 | static let allTests = [
18 | ("testPageCoordinator", testPageCoordinator),
19 | ("testSplitCoordinator", testSplitCoordinator),
20 | ("testTabBarCoordinator", testTabBarCoordinator),
21 | ("testViewCoordinator", testViewCoordinator),
22 | ("testNavigationCoordinator", testNavigationCoordinator),
23 | ]
24 |
25 | // MARK: Stored properties
26 |
27 | private lazy var window = UIWindow()
28 |
29 | // MARK: Tests
30 |
31 | func testViewCoordinator() {
32 | let coordinator = ViewCoordinator(rootViewController: .init())
33 | coordinator.setRoot(for: window)
34 | testStandardAnimationsCalled(on: coordinator)
35 | }
36 |
37 | func testSplitCoordinator() {
38 | let coordinator = SplitCoordinator(master: UIViewController(), detail: UIViewController())
39 | coordinator.setRoot(for: window)
40 | testStandardAnimationsCalled(on: coordinator)
41 | }
42 |
43 | func testPageCoordinator() {
44 | let coordinator = PageCoordinator(pages: [UIViewController()])
45 | coordinator.setRoot(for: window)
46 | testStandardAnimationsCalled(on: coordinator)
47 | }
48 |
49 | func testTabBarCoordinator() {
50 | let tabs = [UIViewController(), UIViewController(), UIViewController()]
51 | let coordinator = TabBarCoordinator(tabs: tabs)
52 | coordinator.setRoot(for: window)
53 | testStandardAnimationsCalled(on: coordinator)
54 |
55 | testStaticAnimationCalled(on: coordinator, transition: { .select(tabs[1], animation: $0) })
56 | testInteractiveAnimationCalled(on: coordinator, transition: { .select(tabs[2], animation: $0) })
57 |
58 | testStaticAnimationCalled(on: coordinator, transition: { .select(index: 1, animation: $0) })
59 | testInteractiveAnimationCalled(on: coordinator, transition: { .select(index: 2, animation: $0) })
60 |
61 | testStaticAnimationCalled(
62 | on: coordinator,
63 | transition: { .set([UIViewController(), UIViewController()], animation: $0) }
64 | )
65 | testInteractiveAnimationCalled(
66 | on: coordinator,
67 | transition: { .set([UIViewController(), UIViewController()], animation: $0) }
68 | )
69 | }
70 |
71 | func testNavigationCoordinator() {
72 | let coordinator = NavigationCoordinator(root: UIViewController())
73 | coordinator.setRoot(for: window)
74 | testStandardAnimationsCalled(on: coordinator)
75 |
76 | testStaticAnimationCalled(on: coordinator, transition: { .push(UIViewController(), animation: $0) })
77 | testStaticAnimationCalled(on: coordinator, transition: { .pop(animation: $0) })
78 |
79 | testInteractiveAnimationCalled(on: coordinator, transition: { .push(UIViewController(), animation: $0) })
80 | testInteractiveAnimationCalled(on: coordinator, transition: { .pop(animation: $0) })
81 |
82 | testStaticAnimationCalled(on: coordinator, transition: { .push(UIViewController(), animation: $0) })
83 | testStaticAnimationCalled(on: coordinator, transition: { .push(UIViewController(), animation: $0) })
84 | testStaticAnimationCalled(on: coordinator, transition: { .popToRoot(animation: $0) })
85 |
86 | testInteractiveAnimationCalled(on: coordinator, transition: { .push(UIViewController(), animation: $0) })
87 | testInteractiveAnimationCalled(on: coordinator, transition: { .push(UIViewController(), animation: $0) })
88 | testInteractiveAnimationCalled(on: coordinator, transition: { .popToRoot(animation: $0) })
89 |
90 | let staticViewControllers = [UIViewController(), UIViewController()]
91 | testStaticAnimationCalled(on: coordinator, transition: { .set(staticViewControllers, animation: $0) })
92 | testStaticAnimationCalled(on: coordinator, transition: { .pop(to: staticViewControllers[0], animation: $0) })
93 |
94 | let interactiveViewControllers = [UIViewController(), UIViewController()]
95 | testInteractiveAnimationCalled(on: coordinator, transition: { .set(interactiveViewControllers, animation: $0) })
96 | testInteractiveAnimationCalled(
97 | on: coordinator,
98 | transition: { .pop(to: interactiveViewControllers[0], animation: $0) }
99 | )
100 | }
101 |
102 | // MARK: Helpers
103 |
104 | private func testStandardAnimationsCalled(on coordinator: C) where C.TransitionType == Transition {
105 | testStaticAnimationCalled(on: coordinator, transition: { .present(UIViewController(), animation: $0) })
106 | testStaticAnimationCalled(on: coordinator, transition: { .dismiss(animation: $0) })
107 | testStaticAnimationCalled(
108 | on: coordinator,
109 | transition: { .multiple(.present(UIViewController(), animation: nil), .dismiss(animation: $0)) }
110 | )
111 | testStaticAnimationCalled(
112 | on: coordinator,
113 | transition: { .multiple(.present(UIViewController(), animation: $0), .dismiss(animation: .default)) }
114 | )
115 |
116 | testInteractiveAnimationCalled(on: coordinator, transition: { .present(UIViewController(), animation: $0) })
117 | testInteractiveAnimationCalled(on: coordinator, transition: { .dismiss(animation: $0) })
118 | testInteractiveAnimationCalled(
119 | on: coordinator,
120 | transition: { .multiple(.present(UIViewController(), animation: $0), .dismiss(animation: .default)) }
121 | )
122 | }
123 |
124 | private func testStaticAnimationCalled(on coordinator: C,
125 | transition: (Animation) -> C.TransitionType) {
126 | let animationExpectation = expectation(description: "Animation \(Date().timeIntervalSince1970)")
127 | let completionExpectation = expectation(description: "Completion \(Date().timeIntervalSince1970)")
128 | let testAnimation = TestAnimation.static(presentation: animationExpectation, dismissal: animationExpectation)
129 | let t = transition(testAnimation)
130 | coordinator.performTransition(t, with: TransitionOptions(animated: true)) {
131 | completionExpectation.fulfill()
132 | }
133 | wait(for: [animationExpectation, completionExpectation], timeout: 3, enforceOrder: true)
134 | asyncWait(for: 0.1)
135 | }
136 |
137 | private func testInteractiveAnimationCalled(on coordinator: C,
138 | transition: (Animation) -> C.TransitionType) {
139 | let animationExpectation = expectation(description: "Animation \(Date().timeIntervalSince1970)")
140 | let completionExpectation = expectation(description: "Completion \(Date().timeIntervalSince1970)")
141 | let testAnimation = TestAnimation.interactive(
142 | presentation: animationExpectation,
143 | dismissal: animationExpectation
144 | )
145 | let t = transition(testAnimation)
146 | coordinator.performTransition(t, with: TransitionOptions(animated: true)) {
147 | completionExpectation.fulfill()
148 | _ = testAnimation
149 | }
150 | wait(for: [animationExpectation, completionExpectation], timeout: 3, enforceOrder: true)
151 | asyncWait(for: 0.1)
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/TestAnimation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestAnimation.swift
3 | // XCoordinator_Tests
4 | //
5 | // Created by Paul Kraft on 16.09.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import XCoordinator
10 | import XCTest
11 |
12 | class TestAnimation: Animation {
13 |
14 | static func `static`(presentation: XCTestExpectation, dismissal: XCTestExpectation) -> TestAnimation {
15 | return TestAnimation(
16 | presentation: TestAnimation.staticTransitionAnimation(for: presentation),
17 | dismissal: TestAnimation.staticTransitionAnimation(for: dismissal)
18 | )
19 | }
20 |
21 | static func interactive(presentation: XCTestExpectation, dismissal: XCTestExpectation) -> TestAnimation {
22 | return TestAnimation(
23 | presentation: TestAnimation.interactiveTransitionAnimation(for: presentation),
24 | dismissal: TestAnimation.interactiveTransitionAnimation(for: dismissal)
25 | )
26 | }
27 |
28 | private static func interactiveTransitionAnimation(for expectation: XCTestExpectation?) -> TransitionAnimation {
29 | return InteractiveTransitionAnimation(duration: 0.1) {
30 | expectation?.fulfill()
31 | $0.completeTransition(true)
32 | }
33 | }
34 |
35 | private static func staticTransitionAnimation(for expectation: XCTestExpectation?) -> TransitionAnimation {
36 | return StaticTransitionAnimation(duration: 0.1) {
37 | expectation?.fulfill()
38 | $0.completeTransition(true)
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/TestRoute.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestRoute.swift
3 | // XCoordinatorTests
4 | //
5 | // Created by Paul Kraft on 16.09.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import XCoordinator
10 |
11 | enum TestRoute: Route {
12 | case home
13 | }
14 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/TransitionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionTests.swift
3 | // XCoordinatorTests
4 | //
5 | // Created by Paul Kraft on 16.09.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCoordinator
11 | import XCTest
12 |
13 | class TransitionTests: XCTestCase {
14 |
15 | // MARK: Static properties
16 |
17 | static let allTests = [
18 | ("testPageCoordinator", testPageCoordinator),
19 | ("testSplitCoordinator", testSplitCoordinator),
20 | ("testTabBarCoordinator", testTabBarCoordinator),
21 | ("testViewCoordinator", testViewCoordinator),
22 | ("testNavigationCoordinator", testNavigationCoordinator),
23 | ]
24 |
25 | // MARK: Stored properties
26 |
27 | private lazy var window = UIWindow()
28 |
29 | // MARK: Tests
30 |
31 | func testPageCoordinator() {
32 | let pages = [UIViewController(), UIViewController(), UIViewController()]
33 | let coordinator = PageCoordinator(pages: pages)
34 | coordinator.setRoot(for: window)
35 | testStandardTransitions(on: coordinator)
36 | testCompletionCalled(on: coordinator, transition: .set(pages[0], direction: .forward))
37 | coordinator.rootViewController.isDoubleSided = true
38 | testCompletionCalled(on: coordinator, transition: .set(pages[1], pages[2], direction: .forward))
39 | }
40 |
41 | func testSplitCoordinator() {
42 | let coordinator = SplitCoordinator(master: UIViewController(), detail: UIViewController())
43 | coordinator.setRoot(for: window)
44 | testStandardTransitions(on: coordinator)
45 | testCompletionCalled(
46 | on: coordinator,
47 | transition: .multiple(.show(UIViewController()), .showDetail(UIViewController()))
48 | )
49 | }
50 |
51 | func testTabBarCoordinator() {
52 | let tabs0 = [UIViewController(), UIViewController()]
53 | let coordinator = TabBarCoordinator(tabs: tabs0)
54 | coordinator.setRoot(for: window)
55 | testStandardTransitions(on: coordinator)
56 | let tabs1 = [UIViewController(), UIViewController()]
57 | testCompletionCalled(on: coordinator, transition: .multiple(.set(tabs1), .select(tabs1[1])))
58 | testCompletionCalled(on: coordinator, transition: .multiple(.set(tabs0), .select(index: 1)))
59 | }
60 |
61 | func testViewCoordinator() {
62 | let coordinator = ViewCoordinator(rootViewController: .init())
63 | coordinator.setRoot(for: window)
64 | testStandardTransitions(on: coordinator)
65 | }
66 |
67 | func testNavigationCoordinator() {
68 | let coordinator = NavigationCoordinator(root: UIViewController())
69 | coordinator.setRoot(for: window)
70 | testStandardTransitions(on: coordinator)
71 | testCompletionCalled(on: coordinator, transition: .push(UIViewController()))
72 | testCompletionCalled(on: coordinator, transition: .pop())
73 | testCompletionCalled(on: coordinator, transition: .push(UIViewController()))
74 | testCompletionCalled(on: coordinator, transition: .popToRoot())
75 |
76 | let viewControllers = [UIViewController(), UIViewController()]
77 | testCompletionCalled(on: coordinator, transition: .set(viewControllers))
78 | testCompletionCalled(on: coordinator, transition: .pop(to: viewControllers[0]))
79 | }
80 |
81 | // MARK: Helpers
82 |
83 | private func testStandardTransitions(on coordinator: C) where C.TransitionType == Transition {
84 | testCompletionCalled(on: coordinator, transition: .none())
85 | testCompletionCalled(on: coordinator, transition: .present(UIViewController()))
86 | testCompletionCalled(on: coordinator, transition: .dismiss())
87 | testCompletionCalled(on: coordinator, transition: .embed(UIViewController(), in: UIViewController()))
88 | testCompletionCalled(on: coordinator, transition: .multiple(.none()))
89 | testCompletionCalled(on: coordinator, transition: .multiple())
90 | }
91 |
92 | private func testCompletionCalled(on coordinator: C, transition: C.TransitionType) {
93 | let exp = expectation(description: Date().timeIntervalSince1970.description)
94 | DispatchQueue.main.async {
95 | coordinator.performTransition(transition, with: .init(animated: true)) {
96 | exp.fulfill()
97 | }
98 | }
99 | wait(for: [exp], timeout: 3)
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(AnimationTests.allTests),
7 | testCase(TransitionTests.allTests)
8 | ]
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/XCText+Extras.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCText+Extras.swift
3 | // XCoordinator_Tests
4 | //
5 | // Created by Paul Kraft on 20.11.18.
6 | // Copyright © 2018 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | extension XCTestCase {
13 |
14 | func asyncWait(for timeInterval: TimeInterval) {
15 | let waitExpectation = self.expectation(description: "WAIT \(Date().timeIntervalSince1970)")
16 | DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + timeInterval) {
17 | waitExpectation.fulfill()
18 | }
19 | wait(for: [waitExpectation], timeout: max(timeInterval * 2, 1))
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleTests/XCoordinator-Example.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "1ED5FC9F-923F-4E5B-B12A-422420D973BC",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "codeCoverage" : false,
13 | "targetForVariableExpansion" : {
14 | "containerPath" : "container:XCoordinator-Example.xcodeproj",
15 | "identifier" : "9B5657342315FA7B00F4F4F7",
16 | "name" : "XCoordinator-Example"
17 | }
18 | },
19 | "testTargets" : [
20 | {
21 | "target" : {
22 | "containerPath" : "container:XCoordinator-Example.xcodeproj",
23 | "identifier" : "9B56574A2315FA7E00F4F4F7",
24 | "name" : "XCoordinator-ExampleTests"
25 | }
26 | },
27 | {
28 | "target" : {
29 | "containerPath" : "container:XCoordinator-Example.xcodeproj",
30 | "identifier" : "9B5657552315FA7E00F4F4F7",
31 | "name" : "XCoordinator-ExampleUITests"
32 | }
33 | }
34 | ],
35 | "version" : 1
36 | }
37 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/XCoordinator-ExampleUITests/XCoordinator_ExampleUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCoordinator_ExampleUITests.swift
3 | // XCoordinator-ExampleUITests
4 | //
5 | // Created by Paul Kraft on 28.08.19.
6 | // Copyright © 2019 QuickBird Studios. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class XCoordinator_ExampleUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // 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.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testExample() {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use recording to get started writing UI tests.
32 | // Use XCTAssert and related functions to verify your tests produce the correct results.
33 | }
34 |
35 | func testLaunchPerformance() {
36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
37 | // This measures how long it takes to launch your application.
38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
39 | XCUIApplication().launch()
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------