├── .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 | --------------------------------------------------------------------------------