├── .gitignore ├── .gitmodules ├── Cartfile ├── Cartfile.resolved ├── RxNextExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ ├── RxNextExample.xcscheme │ │ └── RxNextExampleTests.xcscheme └── xcuserdata │ └── yuki.kokubun.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── RxNextExample ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Catalog │ ├── Entities │ │ └── ExampleScreen.swift │ ├── ViewControllers │ │ └── CagtalogViewController.swift │ ├── ViewModels │ │ └── CatalogViewModel.swift │ ├── Views │ │ └── ExampleScreenCell.swift │ └── Wireframes │ │ └── CatalogWireframe.swift ├── Examples │ ├── DynamicHierarchalViewModelExample │ │ ├── Entity │ │ │ ├── AnswerEntry.swift │ │ │ ├── Quiz.swift │ │ │ └── QuizEntry.swift │ │ ├── Models │ │ │ ├── AllQuizEntriesModel.swift │ │ │ ├── AnswerEntriesModel.swift │ │ │ └── QuizIndexModel.swift │ │ ├── Repositories │ │ │ ├── QuizEntriesConstantRepository.swift │ │ │ ├── QuizEntriesDummyRepository.swift │ │ │ └── QuizEntriesRepository.swift │ │ ├── ViewControllers │ │ │ └── QuizzesViewController.swift │ │ ├── ViewModels │ │ │ ├── QuizViewModel.swift │ │ │ └── QuizzesViewModel.swift │ │ ├── Views │ │ │ ├── QuizCell.swift │ │ │ └── QuizzesRootView.swift │ │ └── Xibs │ │ │ ├── QuizCell.xib │ │ │ └── QuizzesScreen.xib │ ├── SharedModelExample │ │ ├── SelectedBadgesScreen │ │ │ ├── ViewControllers │ │ │ │ └── SelectedBadgesViewController.swift │ │ │ ├── ViewModels │ │ │ │ ├── ReadOnlySelectedBadgesViewModel.swift │ │ │ │ └── SelectedBadgesScreenViewModel.swift │ │ │ ├── Views │ │ │ │ └── SelectedBadgesRootView.swift │ │ │ └── Xibs │ │ │ │ └── SelectedBadgesScreen.xib │ │ ├── SelectingBadgesScreen │ │ │ ├── Models │ │ │ │ ├── AllBadgesModel.swift │ │ │ │ └── SelectableBadgesModel.swift │ │ │ ├── Repositories │ │ │ │ ├── BadgesConstantRepository.swift │ │ │ │ ├── BadgesDummyRepository.swift │ │ │ │ └── BadgesRepository.swift │ │ │ ├── ViewControllers │ │ │ │ └── BadgeSelectorViewController.swift │ │ │ ├── ViewModels │ │ │ │ ├── BadgeSelectorCompletionViewModel.swift │ │ │ │ ├── BadgeSelectorViewModel.swift │ │ │ │ ├── SelectableBadgesViewModel.swift │ │ │ │ └── SelectedBadgesViewModel.swift │ │ │ ├── Views │ │ │ │ ├── BadgeSelectorRootView.swift │ │ │ │ ├── SelectableBadgeCell.swift │ │ │ │ └── SelectedBadgeCell.swift │ │ │ ├── Wireframes │ │ │ │ └── BadgeSelectorWireframe.swift │ │ │ └── Xibs │ │ │ │ ├── BadgeSelectorScreen.xib │ │ │ │ ├── SelectableBadgeCell.xib │ │ │ │ └── SelectedBadgeCell.xib │ │ └── Shared │ │ │ ├── Entities │ │ │ └── Badge.swift │ │ │ └── Models │ │ │ └── SelectedBadgesModel.swift │ └── SimpleExample │ │ ├── ViewControllers │ │ └── SimpleViewController.swift │ │ ├── ViewModels │ │ └── SimpleViewModel.swift │ │ ├── Views │ │ └── SimpleRootView.swift │ │ └── Xibs │ │ └── SimpleScreen.xib ├── Info.plist ├── Shared │ ├── Models │ │ ├── EntityModel.swift │ │ └── StateMachine.swift │ ├── Repositories │ │ └── EntityModelRepository.swift │ ├── Result.swift │ ├── Views │ │ ├── BackButtonTitleHandle.swift │ │ └── Layers │ │ │ └── FilledLayout.swift │ └── Wireframes │ │ └── PresentedViewControllerWireframe.swift └── ViewController.swift └── RxNextExampleTests └── Info.plist /.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 68 | fastlane/test_output 69 | 70 | # R.swift 71 | R.generated.swift 72 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/RxDataSources"] 2 | path = Carthage/Checkouts/RxDataSources 3 | url = https://github.com/RxSwiftCommunity/RxDataSources.git 4 | [submodule "Carthage/Checkouts/RxSwift"] 5 | path = Carthage/Checkouts/RxSwift 6 | url = https://github.com/ReactiveX/RxSwift.git 7 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" ~> 4.0 2 | github "RxSwiftCommunity/RxDataSources" ~> 3.0 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" "4.0.0" 2 | github "RxSwiftCommunity/RxDataSources" "3.0.2" 3 | -------------------------------------------------------------------------------- /RxNextExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2475E002A64581F9CA2E7CC3 /* QuizzesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E75EE64694BA054ACEE5 /* QuizzesViewModel.swift */; }; 11 | 2475E0706748BB2506A8A09B /* SimpleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA3733B470D6D4DB7C4E /* SimpleViewController.swift */; }; 12 | 2475E0A5EDCF66B2FEA37890 /* CagtalogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EF90834D2EA23DE79DDF /* CagtalogViewController.swift */; }; 13 | 2475E12439D42385DDC093D8 /* SimpleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E830B26F2ECB7430304E /* SimpleViewModel.swift */; }; 14 | 2475E1B376441606074D0B3C /* SimpleRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E2AFD0071FE3D9403B6C /* SimpleRootView.swift */; }; 15 | 2475E1E496B3CD824DE80304 /* AnswerEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E3C2261719F100E6B232 /* AnswerEntry.swift */; }; 16 | 2475E209906734D7996A6859 /* QuizzesRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA65C30A5F9454318E52 /* QuizzesRootView.swift */; }; 17 | 2475E26144C1ECC88BD81476 /* AnswerEntriesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E039FA7D16537856DDFD /* AnswerEntriesModel.swift */; }; 18 | 2475E2BBB2C041880A2424F8 /* CatalogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475ECDFEF1C57AE3D0BC3B6 /* CatalogViewModel.swift */; }; 19 | 2475E30D6C78380ECA4E10FD /* BadgeSelectorCompletionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E2C1107EBAAD937485B4 /* BadgeSelectorCompletionViewModel.swift */; }; 20 | 2475E3C00802EC45C68A660B /* ExampleScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EEEB9944D294D8DD63F8 /* ExampleScreenCell.swift */; }; 21 | 2475E407C01F514B2AA768A1 /* QuizEntriesConstantRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA5A2300E1EBCA454F9F /* QuizEntriesConstantRepository.swift */; }; 22 | 2475E4DB215AF09D13F9B473 /* BadgesRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA5813EB391AF0AED436 /* BadgesRepository.swift */; }; 23 | 2475E4F9EBF85E418B85D734 /* SelectedBadgesScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E33A1515E2135B3FBACA /* SelectedBadgesScreenViewModel.swift */; }; 24 | 2475E50A7CFEBB77A436B65B /* QuizEntriesRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E6EE970F30BE2EDA6780 /* QuizEntriesRepository.swift */; }; 25 | 2475E52E29C2F86876E3AEAF /* SelectedBadgesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA5439CF6DA282C88D3F /* SelectedBadgesModel.swift */; }; 26 | 2475E5651F21212D8AF16581 /* QuizEntriesDummyRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E2C85F65E1C0B1095CAA /* QuizEntriesDummyRepository.swift */; }; 27 | 2475E586949D07E4B1B80FC2 /* QuizCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E0F2A0D494E6F1D3878C /* QuizCell.swift */; }; 28 | 2475E65A4592222649D2DB03 /* QuizIndexModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E50C642EB9A80004B7B4 /* QuizIndexModel.swift */; }; 29 | 2475E6D5D138FBB7467CCBBE /* AllBadgesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E26D762789A41F7381C8 /* AllBadgesModel.swift */; }; 30 | 2475E748DEFA2B9EEA0ADC5B /* SelectedBadgesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EF7FE9919060FAD1DB0F /* SelectedBadgesViewController.swift */; }; 31 | 2475E753BC511A44A923A701 /* CatalogWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EB35F6C09F99FDC6FA4C /* CatalogWireframe.swift */; }; 32 | 2475E77E91BA56D82E197073 /* BadgeSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EC16407CD80F3C5BD072 /* BadgeSelectorViewController.swift */; }; 33 | 2475E7CDBFFB56D56D1E2C7C /* QuizzesScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2475E0FBBA57290966D37E6F /* QuizzesScreen.xib */; }; 34 | 2475E88830751472E5755A2C /* ReadOnlySelectedBadgesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E780B255372FFB809673 /* ReadOnlySelectedBadgesViewModel.swift */; }; 35 | 2475E9646C52D664E953AA24 /* PresentedViewControllerWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E69C065B5C74DF5A5EAC /* PresentedViewControllerWireframe.swift */; }; 36 | 2475E9745ED9B2A96B7AFEBB /* FilledLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E8BEC361B9ECE15D9691 /* FilledLayout.swift */; }; 37 | 2475E997324E6D0DA24970EF /* SelectableBadgesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E06C191761DE087B77DB /* SelectableBadgesModel.swift */; }; 38 | 2475E9D2D367CAB555230F8D /* Quiz.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EF40B84C7646E77A1544 /* Quiz.swift */; }; 39 | 2475E9F20CE2662C547C545D /* EntityModelRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E330DE18313B1E52885B /* EntityModelRepository.swift */; }; 40 | 2475E9FACB90260C2966DEB2 /* BadgeSelectorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E5B84F65615616BB7D28 /* BadgeSelectorViewModel.swift */; }; 41 | 2475EA3B82C58CF9A3CF2AC7 /* BackButtonTitleHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E90616FAB610AC4A55C4 /* BackButtonTitleHandle.swift */; }; 42 | 2475EAB1BABFDAD643B41ACD /* BadgeSelectorScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2475EDB3551E5355DF6172C8 /* BadgeSelectorScreen.xib */; }; 43 | 2475EB05637FB6DE3C195BF2 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EB4BC54BEE93E1D3BF33 /* Result.swift */; }; 44 | 2475EB12D9757809CE9638D2 /* SelectableBadgeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2475E6815D37F74FAEEBD2D1 /* SelectableBadgeCell.xib */; }; 45 | 2475EB276A1EE5AE1902AAEE /* SelectedBadgeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EC06DF586F923ED349C6 /* SelectedBadgeCell.swift */; }; 46 | 2475EB4EA3A3057D065BE172 /* BadgesDummyRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EBFF111B9E3EE7B6BF5E /* BadgesDummyRepository.swift */; }; 47 | 2475EB8973B0F90253C498C5 /* AllQuizEntriesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E22EB0923F8110892C5D /* AllQuizEntriesModel.swift */; }; 48 | 2475EB9E804489F8850F398F /* SelectableBadgesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E29188F41113DBFC9C8B /* SelectableBadgesViewModel.swift */; }; 49 | 2475EBAA1FAB75647AFB544E /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E4BEA4BA54B9AE4E1426 /* StateMachine.swift */; }; 50 | 2475EC18D355C33757BE8495 /* QuizEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E3EB20A2087DE1585C20 /* QuizEntry.swift */; }; 51 | 2475EC694FE8D439410A3D65 /* BadgeSelectorWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E7448E7669FF244E65A7 /* BadgeSelectorWireframe.swift */; }; 52 | 2475ECC977C636F7FA4EBDB2 /* SelectableBadgeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EBEC3E5D2696EDC50CC3 /* SelectableBadgeCell.swift */; }; 53 | 2475ECFF2EA811626BE1CD86 /* SelectedBadgesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E198073524402DD21AB1 /* SelectedBadgesViewModel.swift */; }; 54 | 2475ED07A1A02A40BB2D37F9 /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E896740B188A82019D70 /* Badge.swift */; }; 55 | 2475ED142FA3FF2EB09309C6 /* QuizzesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E8E6FA20567705E947B0 /* QuizzesViewController.swift */; }; 56 | 2475EE1135765509F83486BF /* EntityModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EDCC44C2F6EB40C05047 /* EntityModel.swift */; }; 57 | 2475EE13B66C1D21B2C5BABD /* SelectedBadgeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2475EC5E4BD342E6C6127FCA /* SelectedBadgeCell.xib */; }; 58 | 2475EE2DC7DB40BB97DF1C19 /* BadgesConstantRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EE97FEA0055B84C6D769 /* BadgesConstantRepository.swift */; }; 59 | 2475EE5CDEDC8A6482EDD4DE /* QuizViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E689B772899E837AE06A /* QuizViewModel.swift */; }; 60 | 2475EF1622FFC5184B503934 /* BadgeSelectorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EAC533FDEA04E50DC937 /* BadgeSelectorRootView.swift */; }; 61 | 2475EF74469D2819400615EE /* ExampleScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E1545E5AB0690C66441C /* ExampleScreen.swift */; }; 62 | 629472B21FDAB0E000239AD5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629472B11FDAB0E000239AD5 /* AppDelegate.swift */; }; 63 | 629472B41FDAB0E000239AD5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629472B31FDAB0E000239AD5 /* ViewController.swift */; }; 64 | 629472B71FDAB0E000239AD5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 629472B51FDAB0E000239AD5 /* Main.storyboard */; }; 65 | 629472B91FDAB0E000239AD5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 629472B81FDAB0E000239AD5 /* Assets.xcassets */; }; 66 | 629472BC1FDAB0E000239AD5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 629472BA1FDAB0E000239AD5 /* LaunchScreen.storyboard */; }; 67 | 629472DB1FDAB14900239AD5 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 629472D81FDAB14900239AD5 /* RxSwift.framework */; }; 68 | 629472DD1FDAB14900239AD5 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 629472DA1FDAB14900239AD5 /* RxCocoa.framework */; }; 69 | 629472DF1FDAB15700239AD5 /* RxBlocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 629472DE1FDAB15700239AD5 /* RxBlocking.framework */; }; 70 | 629472E71FDB6E9100239AD5 /* Differentiator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 629472E51FDB6E9100239AD5 /* Differentiator.framework */; }; 71 | 629472E81FDB6E9100239AD5 /* RxDataSources.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 629472E61FDB6E9100239AD5 /* RxDataSources.framework */; }; 72 | 62C7B3C01FE9082700031799 /* SimpleScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62C7B3BF1FE9082700031799 /* SimpleScreen.xib */; }; 73 | 62EDEE211FE5343F009DFF38 /* SelectedBadgesScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62EDEE201FE5343F009DFF38 /* SelectedBadgesScreen.xib */; }; 74 | 62EDEE221FE5346E009DFF38 /* SelectedBadgesRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E63B44CE1FA72F97B047 /* SelectedBadgesRootView.swift */; }; 75 | 62EDEE281FE615F0009DFF38 /* QuizCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62EDEE271FE615F0009DFF38 /* QuizCell.xib */; }; 76 | /* End PBXBuildFile section */ 77 | 78 | /* Begin PBXContainerItemProxy section */ 79 | 629472C31FDAB0E000239AD5 /* PBXContainerItemProxy */ = { 80 | isa = PBXContainerItemProxy; 81 | containerPortal = 629472A61FDAB0E000239AD5 /* Project object */; 82 | proxyType = 1; 83 | remoteGlobalIDString = 629472AD1FDAB0E000239AD5; 84 | remoteInfo = RxNextExample; 85 | }; 86 | /* End PBXContainerItemProxy section */ 87 | 88 | /* Begin PBXFileReference section */ 89 | 2475E039FA7D16537856DDFD /* AnswerEntriesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnswerEntriesModel.swift; sourceTree = ""; }; 90 | 2475E06C191761DE087B77DB /* SelectableBadgesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectableBadgesModel.swift; sourceTree = ""; }; 91 | 2475E0F2A0D494E6F1D3878C /* QuizCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizCell.swift; sourceTree = ""; }; 92 | 2475E0FBBA57290966D37E6F /* QuizzesScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuizzesScreen.xib; sourceTree = ""; }; 93 | 2475E1545E5AB0690C66441C /* ExampleScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleScreen.swift; sourceTree = ""; }; 94 | 2475E198073524402DD21AB1 /* SelectedBadgesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectedBadgesViewModel.swift; sourceTree = ""; }; 95 | 2475E22EB0923F8110892C5D /* AllQuizEntriesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllQuizEntriesModel.swift; sourceTree = ""; }; 96 | 2475E26D762789A41F7381C8 /* AllBadgesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllBadgesModel.swift; sourceTree = ""; }; 97 | 2475E29188F41113DBFC9C8B /* SelectableBadgesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectableBadgesViewModel.swift; sourceTree = ""; }; 98 | 2475E2AFD0071FE3D9403B6C /* SimpleRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleRootView.swift; sourceTree = ""; }; 99 | 2475E2C1107EBAAD937485B4 /* BadgeSelectorCompletionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeSelectorCompletionViewModel.swift; sourceTree = ""; }; 100 | 2475E2C85F65E1C0B1095CAA /* QuizEntriesDummyRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizEntriesDummyRepository.swift; sourceTree = ""; }; 101 | 2475E330DE18313B1E52885B /* EntityModelRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityModelRepository.swift; sourceTree = ""; }; 102 | 2475E33A1515E2135B3FBACA /* SelectedBadgesScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectedBadgesScreenViewModel.swift; sourceTree = ""; }; 103 | 2475E3C2261719F100E6B232 /* AnswerEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnswerEntry.swift; sourceTree = ""; }; 104 | 2475E3EB20A2087DE1585C20 /* QuizEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizEntry.swift; sourceTree = ""; }; 105 | 2475E4BEA4BA54B9AE4E1426 /* StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachine.swift; sourceTree = ""; }; 106 | 2475E50C642EB9A80004B7B4 /* QuizIndexModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizIndexModel.swift; sourceTree = ""; }; 107 | 2475E5B84F65615616BB7D28 /* BadgeSelectorViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeSelectorViewModel.swift; sourceTree = ""; }; 108 | 2475E63B44CE1FA72F97B047 /* SelectedBadgesRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectedBadgesRootView.swift; sourceTree = ""; }; 109 | 2475E6815D37F74FAEEBD2D1 /* SelectableBadgeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectableBadgeCell.xib; sourceTree = ""; }; 110 | 2475E689B772899E837AE06A /* QuizViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizViewModel.swift; sourceTree = ""; }; 111 | 2475E69C065B5C74DF5A5EAC /* PresentedViewControllerWireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentedViewControllerWireframe.swift; sourceTree = ""; }; 112 | 2475E6EE970F30BE2EDA6780 /* QuizEntriesRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizEntriesRepository.swift; sourceTree = ""; }; 113 | 2475E7448E7669FF244E65A7 /* BadgeSelectorWireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeSelectorWireframe.swift; sourceTree = ""; }; 114 | 2475E75EE64694BA054ACEE5 /* QuizzesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizzesViewModel.swift; sourceTree = ""; }; 115 | 2475E780B255372FFB809673 /* ReadOnlySelectedBadgesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadOnlySelectedBadgesViewModel.swift; sourceTree = ""; }; 116 | 2475E830B26F2ECB7430304E /* SimpleViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleViewModel.swift; sourceTree = ""; }; 117 | 2475E896740B188A82019D70 /* Badge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = ""; }; 118 | 2475E8BEC361B9ECE15D9691 /* FilledLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilledLayout.swift; sourceTree = ""; }; 119 | 2475E8E6FA20567705E947B0 /* QuizzesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizzesViewController.swift; sourceTree = ""; }; 120 | 2475E90616FAB610AC4A55C4 /* BackButtonTitleHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackButtonTitleHandle.swift; sourceTree = ""; }; 121 | 2475EA3733B470D6D4DB7C4E /* SimpleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleViewController.swift; sourceTree = ""; }; 122 | 2475EA5439CF6DA282C88D3F /* SelectedBadgesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectedBadgesModel.swift; sourceTree = ""; }; 123 | 2475EA5813EB391AF0AED436 /* BadgesRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgesRepository.swift; sourceTree = ""; }; 124 | 2475EA5A2300E1EBCA454F9F /* QuizEntriesConstantRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizEntriesConstantRepository.swift; sourceTree = ""; }; 125 | 2475EA65C30A5F9454318E52 /* QuizzesRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuizzesRootView.swift; sourceTree = ""; }; 126 | 2475EAC533FDEA04E50DC937 /* BadgeSelectorRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeSelectorRootView.swift; sourceTree = ""; }; 127 | 2475EB35F6C09F99FDC6FA4C /* CatalogWireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CatalogWireframe.swift; sourceTree = ""; }; 128 | 2475EB4BC54BEE93E1D3BF33 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 129 | 2475EBEC3E5D2696EDC50CC3 /* SelectableBadgeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectableBadgeCell.swift; sourceTree = ""; }; 130 | 2475EBFF111B9E3EE7B6BF5E /* BadgesDummyRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgesDummyRepository.swift; sourceTree = ""; }; 131 | 2475EC06DF586F923ED349C6 /* SelectedBadgeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectedBadgeCell.swift; sourceTree = ""; }; 132 | 2475EC16407CD80F3C5BD072 /* BadgeSelectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeSelectorViewController.swift; sourceTree = ""; }; 133 | 2475EC5E4BD342E6C6127FCA /* SelectedBadgeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectedBadgeCell.xib; sourceTree = ""; }; 134 | 2475ECDFEF1C57AE3D0BC3B6 /* CatalogViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CatalogViewModel.swift; sourceTree = ""; }; 135 | 2475EDB3551E5355DF6172C8 /* BadgeSelectorScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BadgeSelectorScreen.xib; sourceTree = ""; }; 136 | 2475EDCC44C2F6EB40C05047 /* EntityModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityModel.swift; sourceTree = ""; }; 137 | 2475EE97FEA0055B84C6D769 /* BadgesConstantRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgesConstantRepository.swift; sourceTree = ""; }; 138 | 2475EEEB9944D294D8DD63F8 /* ExampleScreenCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleScreenCell.swift; sourceTree = ""; }; 139 | 2475EF40B84C7646E77A1544 /* Quiz.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quiz.swift; sourceTree = ""; }; 140 | 2475EF7FE9919060FAD1DB0F /* SelectedBadgesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectedBadgesViewController.swift; sourceTree = ""; }; 141 | 2475EF90834D2EA23DE79DDF /* CagtalogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CagtalogViewController.swift; sourceTree = ""; }; 142 | 629472AE1FDAB0E000239AD5 /* RxNextExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxNextExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 143 | 629472B11FDAB0E000239AD5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 144 | 629472B31FDAB0E000239AD5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 145 | 629472B61FDAB0E000239AD5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 146 | 629472B81FDAB0E000239AD5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 147 | 629472BB1FDAB0E000239AD5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 148 | 629472BD1FDAB0E000239AD5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 149 | 629472C21FDAB0E000239AD5 /* RxNextExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxNextExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 150 | 629472C81FDAB0E000239AD5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 151 | 629472D21FDAB13900239AD5 /* Rswift.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Rswift.framework.dSYM; path = Carthage/Build/iOS/Rswift.framework.dSYM; sourceTree = ""; }; 152 | 629472D31FDAB13900239AD5 /* RxCocoa.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = RxCocoa.framework.dSYM; path = Carthage/Build/iOS/RxCocoa.framework.dSYM; sourceTree = ""; }; 153 | 629472D41FDAB13900239AD5 /* RxSwift.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = RxSwift.framework.dSYM; path = Carthage/Build/iOS/RxSwift.framework.dSYM; sourceTree = ""; }; 154 | 629472D81FDAB14900239AD5 /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = ""; }; 155 | 629472D91FDAB14900239AD5 /* Rswift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Rswift.framework; path = Carthage/Build/iOS/Rswift.framework; sourceTree = ""; }; 156 | 629472DA1FDAB14900239AD5 /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = Carthage/Build/iOS/RxCocoa.framework; sourceTree = ""; }; 157 | 629472DE1FDAB15700239AD5 /* RxBlocking.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxBlocking.framework; path = Carthage/Build/iOS/RxBlocking.framework; sourceTree = ""; }; 158 | 629472E51FDB6E9100239AD5 /* Differentiator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Differentiator.framework; path = Carthage/Build/iOS/Differentiator.framework; sourceTree = ""; }; 159 | 629472E61FDB6E9100239AD5 /* RxDataSources.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxDataSources.framework; path = Carthage/Build/iOS/RxDataSources.framework; sourceTree = ""; }; 160 | 62C7B3BF1FE9082700031799 /* SimpleScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SimpleScreen.xib; sourceTree = ""; }; 161 | 62EDEE201FE5343F009DFF38 /* SelectedBadgesScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SelectedBadgesScreen.xib; sourceTree = ""; }; 162 | 62EDEE271FE615F0009DFF38 /* QuizCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuizCell.xib; sourceTree = ""; }; 163 | /* End PBXFileReference section */ 164 | 165 | /* Begin PBXFrameworksBuildPhase section */ 166 | 629472AB1FDAB0E000239AD5 /* Frameworks */ = { 167 | isa = PBXFrameworksBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 629472E71FDB6E9100239AD5 /* Differentiator.framework in Frameworks */, 171 | 629472E81FDB6E9100239AD5 /* RxDataSources.framework in Frameworks */, 172 | 629472DB1FDAB14900239AD5 /* RxSwift.framework in Frameworks */, 173 | 629472DD1FDAB14900239AD5 /* RxCocoa.framework in Frameworks */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | 629472BF1FDAB0E000239AD5 /* Frameworks */ = { 178 | isa = PBXFrameworksBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 629472DF1FDAB15700239AD5 /* RxBlocking.framework in Frameworks */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXFrameworksBuildPhase section */ 186 | 187 | /* Begin PBXGroup section */ 188 | 2475E0549F856B01DB84CAA4 /* Repositories */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 2475EA5A2300E1EBCA454F9F /* QuizEntriesConstantRepository.swift */, 192 | 2475E2C85F65E1C0B1095CAA /* QuizEntriesDummyRepository.swift */, 193 | 2475E6EE970F30BE2EDA6780 /* QuizEntriesRepository.swift */, 194 | ); 195 | path = Repositories; 196 | sourceTree = ""; 197 | }; 198 | 2475E0638839958FA9A51D9E /* ViewModels */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 2475E2C1107EBAAD937485B4 /* BadgeSelectorCompletionViewModel.swift */, 202 | 2475E5B84F65615616BB7D28 /* BadgeSelectorViewModel.swift */, 203 | 2475E29188F41113DBFC9C8B /* SelectableBadgesViewModel.swift */, 204 | 2475E198073524402DD21AB1 /* SelectedBadgesViewModel.swift */, 205 | ); 206 | path = ViewModels; 207 | sourceTree = ""; 208 | }; 209 | 2475E0927E1615209FF56B27 /* Wireframes */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 2475EB35F6C09F99FDC6FA4C /* CatalogWireframe.swift */, 213 | ); 214 | path = Wireframes; 215 | sourceTree = ""; 216 | }; 217 | 2475E12EC4046E8643E974F5 /* ViewModels */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | 2475E689B772899E837AE06A /* QuizViewModel.swift */, 221 | 2475E75EE64694BA054ACEE5 /* QuizzesViewModel.swift */, 222 | ); 223 | path = ViewModels; 224 | sourceTree = ""; 225 | }; 226 | 2475E1AEE7FCD802ADF6F4AB /* Wireframes */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 2475E7448E7669FF244E65A7 /* BadgeSelectorWireframe.swift */, 230 | ); 231 | path = Wireframes; 232 | sourceTree = ""; 233 | }; 234 | 2475E28331BDCD906318B800 /* Entities */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | 2475E1545E5AB0690C66441C /* ExampleScreen.swift */, 238 | ); 239 | path = Entities; 240 | sourceTree = ""; 241 | }; 242 | 2475E3038DF1576E6EC16B66 /* Entity */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | 2475E3C2261719F100E6B232 /* AnswerEntry.swift */, 246 | 2475EF40B84C7646E77A1544 /* Quiz.swift */, 247 | 2475E3EB20A2087DE1585C20 /* QuizEntry.swift */, 248 | ); 249 | path = Entity; 250 | sourceTree = ""; 251 | }; 252 | 2475E3DAE9A8D47384B66332 /* ViewControllers */ = { 253 | isa = PBXGroup; 254 | children = ( 255 | 2475EC16407CD80F3C5BD072 /* BadgeSelectorViewController.swift */, 256 | ); 257 | path = ViewControllers; 258 | sourceTree = ""; 259 | }; 260 | 2475E5AAC21E64EAB6FD45B5 /* Repositories */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | 2475E330DE18313B1E52885B /* EntityModelRepository.swift */, 264 | ); 265 | path = Repositories; 266 | sourceTree = ""; 267 | }; 268 | 2475E668EF183CFC4C550EA3 /* ViewModels */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | 2475ECDFEF1C57AE3D0BC3B6 /* CatalogViewModel.swift */, 272 | ); 273 | path = ViewModels; 274 | sourceTree = ""; 275 | }; 276 | 2475E68F82ADB53EF749288A /* Entities */ = { 277 | isa = PBXGroup; 278 | children = ( 279 | 2475E896740B188A82019D70 /* Badge.swift */, 280 | ); 281 | path = Entities; 282 | sourceTree = ""; 283 | }; 284 | 2475E6E444A991C5C0B6F800 /* Repositories */ = { 285 | isa = PBXGroup; 286 | children = ( 287 | 2475EE97FEA0055B84C6D769 /* BadgesConstantRepository.swift */, 288 | 2475EBFF111B9E3EE7B6BF5E /* BadgesDummyRepository.swift */, 289 | 2475EA5813EB391AF0AED436 /* BadgesRepository.swift */, 290 | ); 291 | path = Repositories; 292 | sourceTree = ""; 293 | }; 294 | 2475E746BB3964823C88716C /* ViewModels */ = { 295 | isa = PBXGroup; 296 | children = ( 297 | 2475E830B26F2ECB7430304E /* SimpleViewModel.swift */, 298 | ); 299 | path = ViewModels; 300 | sourceTree = ""; 301 | }; 302 | 2475E74FCDAE8D15AE7F47BF /* SelectedBadgesScreen */ = { 303 | isa = PBXGroup; 304 | children = ( 305 | 2475EA8259F9144AD2D4B62A /* ViewControllers */, 306 | 2475EB719A6BB4590902887A /* ViewModels */, 307 | 2475EB484472BBBDA3CC6234 /* Views */, 308 | 2475E93025328BA90158D0B1 /* Xibs */, 309 | ); 310 | path = SelectedBadgesScreen; 311 | sourceTree = ""; 312 | }; 313 | 2475E75156CF53F97EB68347 /* Views */ = { 314 | isa = PBXGroup; 315 | children = ( 316 | 2475EAC533FDEA04E50DC937 /* BadgeSelectorRootView.swift */, 317 | 2475EBEC3E5D2696EDC50CC3 /* SelectableBadgeCell.swift */, 318 | 2475EC06DF586F923ED349C6 /* SelectedBadgeCell.swift */, 319 | ); 320 | path = Views; 321 | sourceTree = ""; 322 | }; 323 | 2475E769D597FC2B82714005 /* Catalog */ = { 324 | isa = PBXGroup; 325 | children = ( 326 | 2475E28331BDCD906318B800 /* Entities */, 327 | 2475EF63511E66F7B681F6DE /* ViewControllers */, 328 | 2475E668EF183CFC4C550EA3 /* ViewModels */, 329 | 2475EE87D4F1F39107E396AA /* Views */, 330 | 2475E0927E1615209FF56B27 /* Wireframes */, 331 | ); 332 | path = Catalog; 333 | sourceTree = ""; 334 | }; 335 | 2475E7FB45877DF3172F89FF /* SharedModelExample */ = { 336 | isa = PBXGroup; 337 | children = ( 338 | 2475E74FCDAE8D15AE7F47BF /* SelectedBadgesScreen */, 339 | 2475EAF848875664D3C63199 /* SelectingBadgesScreen */, 340 | 2475EEC952E83C6B9EEAA322 /* Shared */, 341 | ); 342 | path = SharedModelExample; 343 | sourceTree = ""; 344 | }; 345 | 2475E8E77F6C073C2EE29A66 /* Layers */ = { 346 | isa = PBXGroup; 347 | children = ( 348 | 2475E8BEC361B9ECE15D9691 /* FilledLayout.swift */, 349 | ); 350 | path = Layers; 351 | sourceTree = ""; 352 | }; 353 | 2475E916F8E3BEC8CCA84CBA /* Wireframes */ = { 354 | isa = PBXGroup; 355 | children = ( 356 | 2475E69C065B5C74DF5A5EAC /* PresentedViewControllerWireframe.swift */, 357 | ); 358 | path = Wireframes; 359 | sourceTree = ""; 360 | }; 361 | 2475E93025328BA90158D0B1 /* Xibs */ = { 362 | isa = PBXGroup; 363 | children = ( 364 | 62EDEE201FE5343F009DFF38 /* SelectedBadgesScreen.xib */, 365 | ); 366 | path = Xibs; 367 | sourceTree = ""; 368 | }; 369 | 2475E95967B1D4A768F9857C /* Models */ = { 370 | isa = PBXGroup; 371 | children = ( 372 | 2475E26D762789A41F7381C8 /* AllBadgesModel.swift */, 373 | 2475E06C191761DE087B77DB /* SelectableBadgesModel.swift */, 374 | ); 375 | path = Models; 376 | sourceTree = ""; 377 | }; 378 | 2475EA8259F9144AD2D4B62A /* ViewControllers */ = { 379 | isa = PBXGroup; 380 | children = ( 381 | 2475EF7FE9919060FAD1DB0F /* SelectedBadgesViewController.swift */, 382 | ); 383 | path = ViewControllers; 384 | sourceTree = ""; 385 | }; 386 | 2475EAD3F0DE3A3869F437D6 /* DynamicHierarchalViewModelExample */ = { 387 | isa = PBXGroup; 388 | children = ( 389 | 2475E3038DF1576E6EC16B66 /* Entity */, 390 | 2475EE96652E240BB0AC3F5A /* Models */, 391 | 2475E0549F856B01DB84CAA4 /* Repositories */, 392 | 2475EEAC8827F07D49B21C2E /* ViewControllers */, 393 | 2475E12EC4046E8643E974F5 /* ViewModels */, 394 | 2475EF8A1ADD4C1B547D6A15 /* Views */, 395 | 2475EB21445B40310C20E7FE /* Xibs */, 396 | ); 397 | path = DynamicHierarchalViewModelExample; 398 | sourceTree = ""; 399 | }; 400 | 2475EAF848875664D3C63199 /* SelectingBadgesScreen */ = { 401 | isa = PBXGroup; 402 | children = ( 403 | 2475E95967B1D4A768F9857C /* Models */, 404 | 2475E6E444A991C5C0B6F800 /* Repositories */, 405 | 2475E3DAE9A8D47384B66332 /* ViewControllers */, 406 | 2475E0638839958FA9A51D9E /* ViewModels */, 407 | 2475E75156CF53F97EB68347 /* Views */, 408 | 2475E1AEE7FCD802ADF6F4AB /* Wireframes */, 409 | 2475EF58CDCD1176639F2BC2 /* Xibs */, 410 | ); 411 | path = SelectingBadgesScreen; 412 | sourceTree = ""; 413 | }; 414 | 2475EB21445B40310C20E7FE /* Xibs */ = { 415 | isa = PBXGroup; 416 | children = ( 417 | 62EDEE271FE615F0009DFF38 /* QuizCell.xib */, 418 | 2475E0FBBA57290966D37E6F /* QuizzesScreen.xib */, 419 | ); 420 | path = Xibs; 421 | sourceTree = ""; 422 | }; 423 | 2475EB484472BBBDA3CC6234 /* Views */ = { 424 | isa = PBXGroup; 425 | children = ( 426 | 2475E63B44CE1FA72F97B047 /* SelectedBadgesRootView.swift */, 427 | ); 428 | path = Views; 429 | sourceTree = ""; 430 | }; 431 | 2475EB60B2C510E11336DDBD /* Views */ = { 432 | isa = PBXGroup; 433 | children = ( 434 | 2475E8E77F6C073C2EE29A66 /* Layers */, 435 | 2475E90616FAB610AC4A55C4 /* BackButtonTitleHandle.swift */, 436 | ); 437 | path = Views; 438 | sourceTree = ""; 439 | }; 440 | 2475EB719A6BB4590902887A /* ViewModels */ = { 441 | isa = PBXGroup; 442 | children = ( 443 | 2475E780B255372FFB809673 /* ReadOnlySelectedBadgesViewModel.swift */, 444 | 2475E33A1515E2135B3FBACA /* SelectedBadgesScreenViewModel.swift */, 445 | ); 446 | path = ViewModels; 447 | sourceTree = ""; 448 | }; 449 | 2475EB7ED6B642F10CBB23D9 /* Xibs */ = { 450 | isa = PBXGroup; 451 | children = ( 452 | 62C7B3BF1FE9082700031799 /* SimpleScreen.xib */, 453 | ); 454 | path = Xibs; 455 | sourceTree = ""; 456 | }; 457 | 2475EC33BE0EE64B6794E1B8 /* ViewControllers */ = { 458 | isa = PBXGroup; 459 | children = ( 460 | 2475EA3733B470D6D4DB7C4E /* SimpleViewController.swift */, 461 | ); 462 | path = ViewControllers; 463 | sourceTree = ""; 464 | }; 465 | 2475ED6A6ABDE33133CAA64F /* Examples */ = { 466 | isa = PBXGroup; 467 | children = ( 468 | 2475EAD3F0DE3A3869F437D6 /* DynamicHierarchalViewModelExample */, 469 | 2475E7FB45877DF3172F89FF /* SharedModelExample */, 470 | 2475EDD6CC8EF454D4EC747F /* SimpleExample */, 471 | ); 472 | path = Examples; 473 | sourceTree = ""; 474 | }; 475 | 2475EDD6CC8EF454D4EC747F /* SimpleExample */ = { 476 | isa = PBXGroup; 477 | children = ( 478 | 2475EC33BE0EE64B6794E1B8 /* ViewControllers */, 479 | 2475E746BB3964823C88716C /* ViewModels */, 480 | 2475EB7ED6B642F10CBB23D9 /* Xibs */, 481 | 2475EF4AE83DC4BF74928557 /* Views */, 482 | ); 483 | path = SimpleExample; 484 | sourceTree = ""; 485 | }; 486 | 2475EE723AC2FA101621A22C /* Models */ = { 487 | isa = PBXGroup; 488 | children = ( 489 | 2475EDCC44C2F6EB40C05047 /* EntityModel.swift */, 490 | 2475E4BEA4BA54B9AE4E1426 /* StateMachine.swift */, 491 | ); 492 | path = Models; 493 | sourceTree = ""; 494 | }; 495 | 2475EE87D4F1F39107E396AA /* Views */ = { 496 | isa = PBXGroup; 497 | children = ( 498 | 2475EEEB9944D294D8DD63F8 /* ExampleScreenCell.swift */, 499 | ); 500 | path = Views; 501 | sourceTree = ""; 502 | }; 503 | 2475EE96652E240BB0AC3F5A /* Models */ = { 504 | isa = PBXGroup; 505 | children = ( 506 | 2475E22EB0923F8110892C5D /* AllQuizEntriesModel.swift */, 507 | 2475E039FA7D16537856DDFD /* AnswerEntriesModel.swift */, 508 | 2475E50C642EB9A80004B7B4 /* QuizIndexModel.swift */, 509 | ); 510 | path = Models; 511 | sourceTree = ""; 512 | }; 513 | 2475EEAC8827F07D49B21C2E /* ViewControllers */ = { 514 | isa = PBXGroup; 515 | children = ( 516 | 2475E8E6FA20567705E947B0 /* QuizzesViewController.swift */, 517 | ); 518 | path = ViewControllers; 519 | sourceTree = ""; 520 | }; 521 | 2475EEC952E83C6B9EEAA322 /* Shared */ = { 522 | isa = PBXGroup; 523 | children = ( 524 | 2475E68F82ADB53EF749288A /* Entities */, 525 | 2475EF24841F8FE40D7A0A51 /* Models */, 526 | ); 527 | path = Shared; 528 | sourceTree = ""; 529 | }; 530 | 2475EF24841F8FE40D7A0A51 /* Models */ = { 531 | isa = PBXGroup; 532 | children = ( 533 | 2475EA5439CF6DA282C88D3F /* SelectedBadgesModel.swift */, 534 | ); 535 | path = Models; 536 | sourceTree = ""; 537 | }; 538 | 2475EF4AE83DC4BF74928557 /* Views */ = { 539 | isa = PBXGroup; 540 | children = ( 541 | 2475E2AFD0071FE3D9403B6C /* SimpleRootView.swift */, 542 | ); 543 | path = Views; 544 | sourceTree = ""; 545 | }; 546 | 2475EF58CDCD1176639F2BC2 /* Xibs */ = { 547 | isa = PBXGroup; 548 | children = ( 549 | 2475EDB3551E5355DF6172C8 /* BadgeSelectorScreen.xib */, 550 | 2475E6815D37F74FAEEBD2D1 /* SelectableBadgeCell.xib */, 551 | 2475EC5E4BD342E6C6127FCA /* SelectedBadgeCell.xib */, 552 | ); 553 | path = Xibs; 554 | sourceTree = ""; 555 | }; 556 | 2475EF63511E66F7B681F6DE /* ViewControllers */ = { 557 | isa = PBXGroup; 558 | children = ( 559 | 2475EF90834D2EA23DE79DDF /* CagtalogViewController.swift */, 560 | ); 561 | path = ViewControllers; 562 | sourceTree = ""; 563 | }; 564 | 2475EF8A1ADD4C1B547D6A15 /* Views */ = { 565 | isa = PBXGroup; 566 | children = ( 567 | 2475E0F2A0D494E6F1D3878C /* QuizCell.swift */, 568 | 2475EA65C30A5F9454318E52 /* QuizzesRootView.swift */, 569 | ); 570 | path = Views; 571 | sourceTree = ""; 572 | }; 573 | 2475EF984E6CBC767620C88D /* Shared */ = { 574 | isa = PBXGroup; 575 | children = ( 576 | 2475EE723AC2FA101621A22C /* Models */, 577 | 2475E5AAC21E64EAB6FD45B5 /* Repositories */, 578 | 2475EB60B2C510E11336DDBD /* Views */, 579 | 2475E916F8E3BEC8CCA84CBA /* Wireframes */, 580 | 2475EB4BC54BEE93E1D3BF33 /* Result.swift */, 581 | ); 582 | path = Shared; 583 | sourceTree = ""; 584 | }; 585 | 629472A51FDAB0E000239AD5 = { 586 | isa = PBXGroup; 587 | children = ( 588 | 629472D11FDAB13900239AD5 /* Frameworks */, 589 | 629472AF1FDAB0E000239AD5 /* Products */, 590 | 629472B01FDAB0E000239AD5 /* RxNextExample */, 591 | 629472C51FDAB0E000239AD5 /* RxNextExampleTests */, 592 | ); 593 | sourceTree = ""; 594 | }; 595 | 629472AF1FDAB0E000239AD5 /* Products */ = { 596 | isa = PBXGroup; 597 | children = ( 598 | 629472AE1FDAB0E000239AD5 /* RxNextExample.app */, 599 | 629472C21FDAB0E000239AD5 /* RxNextExampleTests.xctest */, 600 | ); 601 | name = Products; 602 | sourceTree = ""; 603 | }; 604 | 629472B01FDAB0E000239AD5 /* RxNextExample */ = { 605 | isa = PBXGroup; 606 | children = ( 607 | 2475E769D597FC2B82714005 /* Catalog */, 608 | 2475ED6A6ABDE33133CAA64F /* Examples */, 609 | 2475EF984E6CBC767620C88D /* Shared */, 610 | 629472B11FDAB0E000239AD5 /* AppDelegate.swift */, 611 | 629472B81FDAB0E000239AD5 /* Assets.xcassets */, 612 | 629472BD1FDAB0E000239AD5 /* Info.plist */, 613 | 629472BA1FDAB0E000239AD5 /* LaunchScreen.storyboard */, 614 | 629472B51FDAB0E000239AD5 /* Main.storyboard */, 615 | 629472B31FDAB0E000239AD5 /* ViewController.swift */, 616 | ); 617 | path = RxNextExample; 618 | sourceTree = ""; 619 | }; 620 | 629472C51FDAB0E000239AD5 /* RxNextExampleTests */ = { 621 | isa = PBXGroup; 622 | children = ( 623 | 629472C81FDAB0E000239AD5 /* Info.plist */, 624 | ); 625 | path = RxNextExampleTests; 626 | sourceTree = ""; 627 | }; 628 | 629472D11FDAB13900239AD5 /* Frameworks */ = { 629 | isa = PBXGroup; 630 | children = ( 631 | 629472E51FDB6E9100239AD5 /* Differentiator.framework */, 632 | 629472E61FDB6E9100239AD5 /* RxDataSources.framework */, 633 | 629472DE1FDAB15700239AD5 /* RxBlocking.framework */, 634 | 629472D91FDAB14900239AD5 /* Rswift.framework */, 635 | 629472DA1FDAB14900239AD5 /* RxCocoa.framework */, 636 | 629472D81FDAB14900239AD5 /* RxSwift.framework */, 637 | 629472D21FDAB13900239AD5 /* Rswift.framework.dSYM */, 638 | 629472D31FDAB13900239AD5 /* RxCocoa.framework.dSYM */, 639 | 629472D41FDAB13900239AD5 /* RxSwift.framework.dSYM */, 640 | ); 641 | name = Frameworks; 642 | sourceTree = ""; 643 | }; 644 | /* End PBXGroup section */ 645 | 646 | /* Begin PBXNativeTarget section */ 647 | 629472AD1FDAB0E000239AD5 /* RxNextExample */ = { 648 | isa = PBXNativeTarget; 649 | buildConfigurationList = 629472CB1FDAB0E000239AD5 /* Build configuration list for PBXNativeTarget "RxNextExample" */; 650 | buildPhases = ( 651 | 629472AA1FDAB0E000239AD5 /* Sources */, 652 | 629472AB1FDAB0E000239AD5 /* Frameworks */, 653 | 629472AC1FDAB0E000239AD5 /* Resources */, 654 | 629472E01FDAB17200239AD5 /* Carthage Copy Frameworks */, 655 | ); 656 | buildRules = ( 657 | ); 658 | dependencies = ( 659 | ); 660 | name = RxNextExample; 661 | productName = RxNextExample; 662 | productReference = 629472AE1FDAB0E000239AD5 /* RxNextExample.app */; 663 | productType = "com.apple.product-type.application"; 664 | }; 665 | 629472C11FDAB0E000239AD5 /* RxNextExampleTests */ = { 666 | isa = PBXNativeTarget; 667 | buildConfigurationList = 629472CE1FDAB0E000239AD5 /* Build configuration list for PBXNativeTarget "RxNextExampleTests" */; 668 | buildPhases = ( 669 | 629472BE1FDAB0E000239AD5 /* Sources */, 670 | 629472BF1FDAB0E000239AD5 /* Frameworks */, 671 | 629472C01FDAB0E000239AD5 /* Resources */, 672 | 629472E21FDAB25600239AD5 /* Carthage Copy Frameworks */, 673 | ); 674 | buildRules = ( 675 | ); 676 | dependencies = ( 677 | 629472C41FDAB0E000239AD5 /* PBXTargetDependency */, 678 | ); 679 | name = RxNextExampleTests; 680 | productName = RxNextExampleTests; 681 | productReference = 629472C21FDAB0E000239AD5 /* RxNextExampleTests.xctest */; 682 | productType = "com.apple.product-type.bundle.unit-test"; 683 | }; 684 | /* End PBXNativeTarget section */ 685 | 686 | /* Begin PBXProject section */ 687 | 629472A61FDAB0E000239AD5 /* Project object */ = { 688 | isa = PBXProject; 689 | attributes = { 690 | LastSwiftUpdateCheck = 0910; 691 | LastUpgradeCheck = 0910; 692 | ORGANIZATIONNAME = kuniwak; 693 | TargetAttributes = { 694 | 629472AD1FDAB0E000239AD5 = { 695 | CreatedOnToolsVersion = 9.1; 696 | ProvisioningStyle = Automatic; 697 | }; 698 | 629472C11FDAB0E000239AD5 = { 699 | CreatedOnToolsVersion = 9.1; 700 | ProvisioningStyle = Automatic; 701 | TestTargetID = 629472AD1FDAB0E000239AD5; 702 | }; 703 | }; 704 | }; 705 | buildConfigurationList = 629472A91FDAB0E000239AD5 /* Build configuration list for PBXProject "RxNextExample" */; 706 | compatibilityVersion = "Xcode 8.0"; 707 | developmentRegion = en; 708 | hasScannedForEncodings = 0; 709 | knownRegions = ( 710 | en, 711 | Base, 712 | ); 713 | mainGroup = 629472A51FDAB0E000239AD5; 714 | productRefGroup = 629472AF1FDAB0E000239AD5 /* Products */; 715 | projectDirPath = ""; 716 | projectRoot = ""; 717 | targets = ( 718 | 629472AD1FDAB0E000239AD5 /* RxNextExample */, 719 | 629472C11FDAB0E000239AD5 /* RxNextExampleTests */, 720 | ); 721 | }; 722 | /* End PBXProject section */ 723 | 724 | /* Begin PBXResourcesBuildPhase section */ 725 | 629472AC1FDAB0E000239AD5 /* Resources */ = { 726 | isa = PBXResourcesBuildPhase; 727 | buildActionMask = 2147483647; 728 | files = ( 729 | 629472BC1FDAB0E000239AD5 /* LaunchScreen.storyboard in Resources */, 730 | 62C7B3C01FE9082700031799 /* SimpleScreen.xib in Resources */, 731 | 629472B91FDAB0E000239AD5 /* Assets.xcassets in Resources */, 732 | 62EDEE281FE615F0009DFF38 /* QuizCell.xib in Resources */, 733 | 629472B71FDAB0E000239AD5 /* Main.storyboard in Resources */, 734 | 2475EAB1BABFDAD643B41ACD /* BadgeSelectorScreen.xib in Resources */, 735 | 2475EE13B66C1D21B2C5BABD /* SelectedBadgeCell.xib in Resources */, 736 | 62EDEE211FE5343F009DFF38 /* SelectedBadgesScreen.xib in Resources */, 737 | 2475EB12D9757809CE9638D2 /* SelectableBadgeCell.xib in Resources */, 738 | 2475E7CDBFFB56D56D1E2C7C /* QuizzesScreen.xib in Resources */, 739 | ); 740 | runOnlyForDeploymentPostprocessing = 0; 741 | }; 742 | 629472C01FDAB0E000239AD5 /* Resources */ = { 743 | isa = PBXResourcesBuildPhase; 744 | buildActionMask = 2147483647; 745 | files = ( 746 | ); 747 | runOnlyForDeploymentPostprocessing = 0; 748 | }; 749 | /* End PBXResourcesBuildPhase section */ 750 | 751 | /* Begin PBXShellScriptBuildPhase section */ 752 | 629472E01FDAB17200239AD5 /* Carthage Copy Frameworks */ = { 753 | isa = PBXShellScriptBuildPhase; 754 | buildActionMask = 2147483647; 755 | files = ( 756 | ); 757 | inputPaths = ( 758 | "$(SRCROOT)/Carthage/Build/iOS/RxSwift.framework", 759 | "$(SRCROOT)/Carthage/Build/iOS/RxCocoa.framework", 760 | "$(SRCROOT)/Carthage/Build/iOS/RxDataSources.framework", 761 | "$(SRCROOT)/Carthage/Build/iOS/Differentiator.framework", 762 | ); 763 | name = "Carthage Copy Frameworks"; 764 | outputPaths = ( 765 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RxSwift.framework", 766 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RxCocoa.framework", 767 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RxDataSources.framework", 768 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Differentiator.framework", 769 | ); 770 | runOnlyForDeploymentPostprocessing = 0; 771 | shellPath = /bin/sh; 772 | shellScript = "/usr/local/bin/carthage copy-frameworks"; 773 | }; 774 | 629472E21FDAB25600239AD5 /* Carthage Copy Frameworks */ = { 775 | isa = PBXShellScriptBuildPhase; 776 | buildActionMask = 2147483647; 777 | files = ( 778 | ); 779 | inputPaths = ( 780 | "$(SRCROOT)/Carthage/Build/iOS/RxBlocking.framework", 781 | ); 782 | name = "Carthage Copy Frameworks"; 783 | outputPaths = ( 784 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RxBlocking.framework", 785 | ); 786 | runOnlyForDeploymentPostprocessing = 0; 787 | shellPath = /bin/sh; 788 | shellScript = "/usr/local/bin/carthage copy-frameworks"; 789 | }; 790 | /* End PBXShellScriptBuildPhase section */ 791 | 792 | /* Begin PBXSourcesBuildPhase section */ 793 | 629472AA1FDAB0E000239AD5 /* Sources */ = { 794 | isa = PBXSourcesBuildPhase; 795 | buildActionMask = 2147483647; 796 | files = ( 797 | 629472B41FDAB0E000239AD5 /* ViewController.swift in Sources */, 798 | 629472B21FDAB0E000239AD5 /* AppDelegate.swift in Sources */, 799 | 2475E3C00802EC45C68A660B /* ExampleScreenCell.swift in Sources */, 800 | 2475EF74469D2819400615EE /* ExampleScreen.swift in Sources */, 801 | 2475E2BBB2C041880A2424F8 /* CatalogViewModel.swift in Sources */, 802 | 2475E0A5EDCF66B2FEA37890 /* CagtalogViewController.swift in Sources */, 803 | 2475E9745ED9B2A96B7AFEBB /* FilledLayout.swift in Sources */, 804 | 2475EE1135765509F83486BF /* EntityModel.swift in Sources */, 805 | 2475EBAA1FAB75647AFB544E /* StateMachine.swift in Sources */, 806 | 2475E9F20CE2662C547C545D /* EntityModelRepository.swift in Sources */, 807 | 2475EB05637FB6DE3C195BF2 /* Result.swift in Sources */, 808 | 62EDEE221FE5346E009DFF38 /* SelectedBadgesRootView.swift in Sources */, 809 | 2475E9FACB90260C2966DEB2 /* BadgeSelectorViewModel.swift in Sources */, 810 | 2475EB9E804489F8850F398F /* SelectableBadgesViewModel.swift in Sources */, 811 | 2475ECFF2EA811626BE1CD86 /* SelectedBadgesViewModel.swift in Sources */, 812 | 2475E77E91BA56D82E197073 /* BadgeSelectorViewController.swift in Sources */, 813 | 2475EF1622FFC5184B503934 /* BadgeSelectorRootView.swift in Sources */, 814 | 2475ECC977C636F7FA4EBDB2 /* SelectableBadgeCell.swift in Sources */, 815 | 2475EB276A1EE5AE1902AAEE /* SelectedBadgeCell.swift in Sources */, 816 | 2475E6D5D138FBB7467CCBBE /* AllBadgesModel.swift in Sources */, 817 | 2475E997324E6D0DA24970EF /* SelectableBadgesModel.swift in Sources */, 818 | 2475E4DB215AF09D13F9B473 /* BadgesRepository.swift in Sources */, 819 | 2475E748DEFA2B9EEA0ADC5B /* SelectedBadgesViewController.swift in Sources */, 820 | 2475E52E29C2F86876E3AEAF /* SelectedBadgesModel.swift in Sources */, 821 | 2475ED07A1A02A40BB2D37F9 /* Badge.swift in Sources */, 822 | 2475E88830751472E5755A2C /* ReadOnlySelectedBadgesViewModel.swift in Sources */, 823 | 2475E30D6C78380ECA4E10FD /* BadgeSelectorCompletionViewModel.swift in Sources */, 824 | 2475EC694FE8D439410A3D65 /* BadgeSelectorWireframe.swift in Sources */, 825 | 2475E753BC511A44A923A701 /* CatalogWireframe.swift in Sources */, 826 | 2475E9646C52D664E953AA24 /* PresentedViewControllerWireframe.swift in Sources */, 827 | 2475E4F9EBF85E418B85D734 /* SelectedBadgesScreenViewModel.swift in Sources */, 828 | 2475EA3B82C58CF9A3CF2AC7 /* BackButtonTitleHandle.swift in Sources */, 829 | 2475EB8973B0F90253C498C5 /* AllQuizEntriesModel.swift in Sources */, 830 | 2475E9D2D367CAB555230F8D /* Quiz.swift in Sources */, 831 | 2475E26144C1ECC88BD81476 /* AnswerEntriesModel.swift in Sources */, 832 | 2475EC18D355C33757BE8495 /* QuizEntry.swift in Sources */, 833 | 2475E1E496B3CD824DE80304 /* AnswerEntry.swift in Sources */, 834 | 2475E002A64581F9CA2E7CC3 /* QuizzesViewModel.swift in Sources */, 835 | 2475ED142FA3FF2EB09309C6 /* QuizzesViewController.swift in Sources */, 836 | 2475E586949D07E4B1B80FC2 /* QuizCell.swift in Sources */, 837 | 2475EE5CDEDC8A6482EDD4DE /* QuizViewModel.swift in Sources */, 838 | 2475E209906734D7996A6859 /* QuizzesRootView.swift in Sources */, 839 | 2475E50A7CFEBB77A436B65B /* QuizEntriesRepository.swift in Sources */, 840 | 2475E65A4592222649D2DB03 /* QuizIndexModel.swift in Sources */, 841 | 2475E407C01F514B2AA768A1 /* QuizEntriesConstantRepository.swift in Sources */, 842 | 2475E5651F21212D8AF16581 /* QuizEntriesDummyRepository.swift in Sources */, 843 | 2475EE2DC7DB40BB97DF1C19 /* BadgesConstantRepository.swift in Sources */, 844 | 2475EB4EA3A3057D065BE172 /* BadgesDummyRepository.swift in Sources */, 845 | 2475E1B376441606074D0B3C /* SimpleRootView.swift in Sources */, 846 | 2475E0706748BB2506A8A09B /* SimpleViewController.swift in Sources */, 847 | 2475E12439D42385DDC093D8 /* SimpleViewModel.swift in Sources */, 848 | ); 849 | runOnlyForDeploymentPostprocessing = 0; 850 | }; 851 | 629472BE1FDAB0E000239AD5 /* Sources */ = { 852 | isa = PBXSourcesBuildPhase; 853 | buildActionMask = 2147483647; 854 | files = ( 855 | ); 856 | runOnlyForDeploymentPostprocessing = 0; 857 | }; 858 | /* End PBXSourcesBuildPhase section */ 859 | 860 | /* Begin PBXTargetDependency section */ 861 | 629472C41FDAB0E000239AD5 /* PBXTargetDependency */ = { 862 | isa = PBXTargetDependency; 863 | target = 629472AD1FDAB0E000239AD5 /* RxNextExample */; 864 | targetProxy = 629472C31FDAB0E000239AD5 /* PBXContainerItemProxy */; 865 | }; 866 | /* End PBXTargetDependency section */ 867 | 868 | /* Begin PBXVariantGroup section */ 869 | 629472B51FDAB0E000239AD5 /* Main.storyboard */ = { 870 | isa = PBXVariantGroup; 871 | children = ( 872 | 629472B61FDAB0E000239AD5 /* Base */, 873 | ); 874 | name = Main.storyboard; 875 | path = .; 876 | sourceTree = ""; 877 | }; 878 | 629472BA1FDAB0E000239AD5 /* LaunchScreen.storyboard */ = { 879 | isa = PBXVariantGroup; 880 | children = ( 881 | 629472BB1FDAB0E000239AD5 /* Base */, 882 | ); 883 | name = LaunchScreen.storyboard; 884 | path = .; 885 | sourceTree = ""; 886 | }; 887 | /* End PBXVariantGroup section */ 888 | 889 | /* Begin XCBuildConfiguration section */ 890 | 629472C91FDAB0E000239AD5 /* Debug */ = { 891 | isa = XCBuildConfiguration; 892 | buildSettings = { 893 | ALWAYS_SEARCH_USER_PATHS = NO; 894 | CLANG_ANALYZER_NONNULL = YES; 895 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 896 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 897 | CLANG_CXX_LIBRARY = "libc++"; 898 | CLANG_ENABLE_MODULES = YES; 899 | CLANG_ENABLE_OBJC_ARC = YES; 900 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 901 | CLANG_WARN_BOOL_CONVERSION = YES; 902 | CLANG_WARN_COMMA = YES; 903 | CLANG_WARN_CONSTANT_CONVERSION = YES; 904 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 905 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 906 | CLANG_WARN_EMPTY_BODY = YES; 907 | CLANG_WARN_ENUM_CONVERSION = YES; 908 | CLANG_WARN_INFINITE_RECURSION = YES; 909 | CLANG_WARN_INT_CONVERSION = YES; 910 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 911 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 912 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 913 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 914 | CLANG_WARN_STRICT_PROTOTYPES = YES; 915 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 916 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 917 | CLANG_WARN_UNREACHABLE_CODE = YES; 918 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 919 | CODE_SIGN_IDENTITY = "iPhone Developer"; 920 | COPY_PHASE_STRIP = NO; 921 | DEBUG_INFORMATION_FORMAT = dwarf; 922 | ENABLE_STRICT_OBJC_MSGSEND = YES; 923 | ENABLE_TESTABILITY = YES; 924 | GCC_C_LANGUAGE_STANDARD = gnu11; 925 | GCC_DYNAMIC_NO_PIC = NO; 926 | GCC_NO_COMMON_BLOCKS = YES; 927 | GCC_OPTIMIZATION_LEVEL = 0; 928 | GCC_PREPROCESSOR_DEFINITIONS = ( 929 | "DEBUG=1", 930 | "$(inherited)", 931 | ); 932 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 933 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 934 | GCC_WARN_UNDECLARED_SELECTOR = YES; 935 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 936 | GCC_WARN_UNUSED_FUNCTION = YES; 937 | GCC_WARN_UNUSED_VARIABLE = YES; 938 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 939 | MTL_ENABLE_DEBUG_INFO = YES; 940 | ONLY_ACTIVE_ARCH = YES; 941 | SDKROOT = iphoneos; 942 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 943 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 944 | }; 945 | name = Debug; 946 | }; 947 | 629472CA1FDAB0E000239AD5 /* Release */ = { 948 | isa = XCBuildConfiguration; 949 | buildSettings = { 950 | ALWAYS_SEARCH_USER_PATHS = NO; 951 | CLANG_ANALYZER_NONNULL = YES; 952 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 953 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 954 | CLANG_CXX_LIBRARY = "libc++"; 955 | CLANG_ENABLE_MODULES = YES; 956 | CLANG_ENABLE_OBJC_ARC = YES; 957 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 958 | CLANG_WARN_BOOL_CONVERSION = YES; 959 | CLANG_WARN_COMMA = YES; 960 | CLANG_WARN_CONSTANT_CONVERSION = YES; 961 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 962 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 963 | CLANG_WARN_EMPTY_BODY = YES; 964 | CLANG_WARN_ENUM_CONVERSION = YES; 965 | CLANG_WARN_INFINITE_RECURSION = YES; 966 | CLANG_WARN_INT_CONVERSION = YES; 967 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 968 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 969 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 970 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 971 | CLANG_WARN_STRICT_PROTOTYPES = YES; 972 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 973 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 974 | CLANG_WARN_UNREACHABLE_CODE = YES; 975 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 976 | CODE_SIGN_IDENTITY = "iPhone Developer"; 977 | COPY_PHASE_STRIP = NO; 978 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 979 | ENABLE_NS_ASSERTIONS = NO; 980 | ENABLE_STRICT_OBJC_MSGSEND = YES; 981 | GCC_C_LANGUAGE_STANDARD = gnu11; 982 | GCC_NO_COMMON_BLOCKS = YES; 983 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 984 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 985 | GCC_WARN_UNDECLARED_SELECTOR = YES; 986 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 987 | GCC_WARN_UNUSED_FUNCTION = YES; 988 | GCC_WARN_UNUSED_VARIABLE = YES; 989 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 990 | MTL_ENABLE_DEBUG_INFO = NO; 991 | SDKROOT = iphoneos; 992 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 993 | VALIDATE_PRODUCT = YES; 994 | }; 995 | name = Release; 996 | }; 997 | 629472CC1FDAB0E000239AD5 /* Debug */ = { 998 | isa = XCBuildConfiguration; 999 | buildSettings = { 1000 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1001 | CODE_SIGN_STYLE = Automatic; 1002 | FRAMEWORK_SEARCH_PATHS = ( 1003 | "$(inherited)", 1004 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1005 | ); 1006 | INFOPLIST_FILE = RxNextExample/Info.plist; 1007 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 1008 | PRODUCT_BUNDLE_IDENTIFIER = com.kuniwak.RxNextExample; 1009 | PRODUCT_NAME = "$(TARGET_NAME)"; 1010 | SWIFT_VERSION = 4.0; 1011 | TARGETED_DEVICE_FAMILY = "1,2"; 1012 | }; 1013 | name = Debug; 1014 | }; 1015 | 629472CD1FDAB0E000239AD5 /* Release */ = { 1016 | isa = XCBuildConfiguration; 1017 | buildSettings = { 1018 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1019 | CODE_SIGN_STYLE = Automatic; 1020 | FRAMEWORK_SEARCH_PATHS = ( 1021 | "$(inherited)", 1022 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1023 | ); 1024 | INFOPLIST_FILE = RxNextExample/Info.plist; 1025 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 1026 | PRODUCT_BUNDLE_IDENTIFIER = com.kuniwak.RxNextExample; 1027 | PRODUCT_NAME = "$(TARGET_NAME)"; 1028 | SWIFT_VERSION = 4.0; 1029 | TARGETED_DEVICE_FAMILY = "1,2"; 1030 | }; 1031 | name = Release; 1032 | }; 1033 | 629472CF1FDAB0E000239AD5 /* Debug */ = { 1034 | isa = XCBuildConfiguration; 1035 | buildSettings = { 1036 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1037 | BUNDLE_LOADER = "$(TEST_HOST)"; 1038 | CODE_SIGN_STYLE = Automatic; 1039 | FRAMEWORK_SEARCH_PATHS = ( 1040 | "$(inherited)", 1041 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1042 | ); 1043 | INFOPLIST_FILE = RxNextExampleTests/Info.plist; 1044 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1045 | PRODUCT_BUNDLE_IDENTIFIER = com.kuniwak.RxNextExampleTests; 1046 | PRODUCT_NAME = "$(TARGET_NAME)"; 1047 | SWIFT_VERSION = 4.0; 1048 | TARGETED_DEVICE_FAMILY = "1,2"; 1049 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxNextExample.app/RxNextExample"; 1050 | }; 1051 | name = Debug; 1052 | }; 1053 | 629472D01FDAB0E000239AD5 /* Release */ = { 1054 | isa = XCBuildConfiguration; 1055 | buildSettings = { 1056 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1057 | BUNDLE_LOADER = "$(TEST_HOST)"; 1058 | CODE_SIGN_STYLE = Automatic; 1059 | FRAMEWORK_SEARCH_PATHS = ( 1060 | "$(inherited)", 1061 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1062 | ); 1063 | INFOPLIST_FILE = RxNextExampleTests/Info.plist; 1064 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1065 | PRODUCT_BUNDLE_IDENTIFIER = com.kuniwak.RxNextExampleTests; 1066 | PRODUCT_NAME = "$(TARGET_NAME)"; 1067 | SWIFT_VERSION = 4.0; 1068 | TARGETED_DEVICE_FAMILY = "1,2"; 1069 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxNextExample.app/RxNextExample"; 1070 | }; 1071 | name = Release; 1072 | }; 1073 | /* End XCBuildConfiguration section */ 1074 | 1075 | /* Begin XCConfigurationList section */ 1076 | 629472A91FDAB0E000239AD5 /* Build configuration list for PBXProject "RxNextExample" */ = { 1077 | isa = XCConfigurationList; 1078 | buildConfigurations = ( 1079 | 629472C91FDAB0E000239AD5 /* Debug */, 1080 | 629472CA1FDAB0E000239AD5 /* Release */, 1081 | ); 1082 | defaultConfigurationIsVisible = 0; 1083 | defaultConfigurationName = Release; 1084 | }; 1085 | 629472CB1FDAB0E000239AD5 /* Build configuration list for PBXNativeTarget "RxNextExample" */ = { 1086 | isa = XCConfigurationList; 1087 | buildConfigurations = ( 1088 | 629472CC1FDAB0E000239AD5 /* Debug */, 1089 | 629472CD1FDAB0E000239AD5 /* Release */, 1090 | ); 1091 | defaultConfigurationIsVisible = 0; 1092 | defaultConfigurationName = Release; 1093 | }; 1094 | 629472CE1FDAB0E000239AD5 /* Build configuration list for PBXNativeTarget "RxNextExampleTests" */ = { 1095 | isa = XCConfigurationList; 1096 | buildConfigurations = ( 1097 | 629472CF1FDAB0E000239AD5 /* Debug */, 1098 | 629472D01FDAB0E000239AD5 /* Release */, 1099 | ); 1100 | defaultConfigurationIsVisible = 0; 1101 | defaultConfigurationName = Release; 1102 | }; 1103 | /* End XCConfigurationList section */ 1104 | }; 1105 | rootObject = 629472A61FDAB0E000239AD5 /* Project object */; 1106 | } 1107 | -------------------------------------------------------------------------------- /RxNextExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RxNextExample.xcodeproj/xcshareddata/xcschemes/RxNextExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RxNextExample.xcodeproj/xcshareddata/xcschemes/RxNextExampleTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /RxNextExample.xcodeproj/xcuserdata/yuki.kokubun.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RxNextExample.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | RxNextExampleTests.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /RxNextExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | @UIApplicationMain 6 | class AppDelegate: UIResponder, UIApplicationDelegate { 7 | var window: UIWindow? 8 | 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 11 | let window = UIWindow() 12 | self.window = window 13 | 14 | let catalogWireframe = DefaultCatalogWireframe.bootstrap(on: window) 15 | catalogWireframe.goToCatalogScreen() 16 | 17 | return true 18 | } 19 | } -------------------------------------------------------------------------------- /RxNextExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /RxNextExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | HelveticaNeue-Bold 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /RxNextExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RxNextExample/Catalog/Entities/ExampleScreen.swift: -------------------------------------------------------------------------------- 1 | enum ExampleScreen { 2 | case simple 3 | case sharedModel 4 | case dynamicHierarchalViewModel 5 | 6 | 7 | var title: String { 8 | switch self { 9 | case .simple: 10 | return "Simple" 11 | case .sharedModel: 12 | return "Shared Model" 13 | case .dynamicHierarchalViewModel: 14 | return "Dynamic Hierarchal ViewModel" 15 | } 16 | } 17 | 18 | 19 | static var all: [ExampleScreen] = [ 20 | .simple, 21 | .sharedModel, 22 | .dynamicHierarchalViewModel, 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /RxNextExample/Catalog/ViewControllers/CagtalogViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxSwift 3 | import RxCocoa 4 | 5 | 6 | 7 | class CatalogViewController: UIViewController { 8 | typealias Dependency = CatalogWireframe 9 | 10 | private let dependency: Dependency 11 | private let tableView: UITableView 12 | private let disposeBag = RxSwift.DisposeBag() 13 | 14 | private var viewModel: CatalogViewModel? 15 | 16 | 17 | init(dependency: Dependency) { 18 | self.dependency = dependency 19 | self.tableView = UITableView() 20 | 21 | super.init(nibName: nil, bundle: nil) 22 | } 23 | 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | return nil 27 | } 28 | 29 | 30 | override func loadView() { 31 | self.view = self.tableView 32 | 33 | self.title = "Examples" 34 | } 35 | 36 | 37 | override func viewDidLoad() { 38 | ExampleScreenCell.register(to: self.tableView) 39 | 40 | let viewModel = CatalogViewModel( 41 | input: self.tableView.rx.itemSelected 42 | .do(onNext: { [weak self] indexPath in 43 | guard let `self` = self else { return } 44 | self.tableView.deselectRow(at: indexPath, animated: true) 45 | }) 46 | .map { indexPath in indexPath.row } 47 | .asSignal(onErrorSignalWith: .empty()), 48 | dependency: self.dependency 49 | ) 50 | self.viewModel = viewModel 51 | 52 | viewModel 53 | .screens 54 | .drive(self.tableView.rx.items( 55 | cellIdentifier: ExampleScreenCell.reuseIdentifier, 56 | cellType: ExampleScreenCell.self 57 | )) { _, screen, cell in 58 | cell.screen = screen 59 | } 60 | .disposed(by: self.disposeBag) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RxNextExample/Catalog/ViewModels/CatalogViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class CatalogViewModel { 7 | typealias Input = RxCocoa.Signal 8 | private let disposeBag = RxSwift.DisposeBag() 9 | 10 | 11 | let screenRelay: RxCocoa.BehaviorRelay<[ExampleScreen]> 12 | let screens: RxCocoa.Driver<[ExampleScreen]> 13 | 14 | 15 | init(input indexSelected: Input, dependency wireframe: CatalogWireframe) { 16 | let screenRelay = BehaviorRelay(value: ExampleScreen.all) 17 | 18 | self.screenRelay = screenRelay 19 | self.screens = screenRelay.asDriver() 20 | 21 | indexSelected 22 | .emit(onNext: { [weak self] index in 23 | guard let `self` = self else { return } 24 | let screens = self.screenRelay.value 25 | 26 | switch screens[index] { 27 | case .simple: 28 | wireframe.goToSimpleExampleScreen() 29 | case .sharedModel: 30 | wireframe.goToSharedModelExampleScreen() 31 | case .dynamicHierarchalViewModel: 32 | wireframe.goToHierarchalViewModelExampleScreen() 33 | } 34 | }) 35 | .disposed(by: self.disposeBag) 36 | } 37 | } -------------------------------------------------------------------------------- /RxNextExample/Catalog/Views/ExampleScreenCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | class ExampleScreenCell: UITableViewCell { 6 | static let reuseIdentifier = "ScreenCell" 7 | 8 | 9 | var screen: ExampleScreen? { 10 | didSet { 11 | self.textLabel?.text = screen?.title 12 | } 13 | } 14 | 15 | 16 | static func register(to tableView: UITableView) { 17 | tableView.register(self, forCellReuseIdentifier: self.reuseIdentifier) 18 | } 19 | 20 | 21 | static func dequeue(from tableView: UITableView, screen: ExampleScreen) -> ExampleScreenCell { 22 | guard let cell = tableView.dequeueReusableCell(withIdentifier: self.reuseIdentifier) as? ExampleScreenCell else { 23 | fatalError("Please register \(self.reuseIdentifier)") 24 | } 25 | 26 | cell.screen = screen 27 | return cell 28 | } 29 | } -------------------------------------------------------------------------------- /RxNextExample/Catalog/Wireframes/CatalogWireframe.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | protocol CatalogWireframe { 6 | func goToCatalogScreen() 7 | func goToSimpleExampleScreen() 8 | func goToSharedModelExampleScreen() 9 | func goToHierarchalViewModelExampleScreen() 10 | } 11 | 12 | 13 | 14 | class DefaultCatalogWireframe: CatalogWireframe { 15 | private weak var navigationController: UINavigationController? 16 | 17 | 18 | init(on navigationController: UINavigationController) { 19 | self.navigationController = navigationController 20 | } 21 | 22 | 23 | func goToCatalogScreen() { 24 | let catalogViewController = CatalogViewController(dependency: self) 25 | BackButtonTitleHandler.hideBackButtonTitle(of: catalogViewController) 26 | 27 | self.navigationController?.setViewControllers([ 28 | catalogViewController 29 | ], animated: false) 30 | } 31 | 32 | 33 | func goToSimpleExampleScreen() { 34 | let viewController = SimpleViewController() 35 | self.navigationController?.pushViewController(viewController, animated: true) 36 | } 37 | 38 | 39 | func goToSharedModelExampleScreen() { 40 | let allBadgesModel = DefaultAllBadgesModel(gettingBadgesVia: BadgesDummyRepository()) 41 | let selectedBadgesModel = DefaultSelectedBadgesModel(selected: []) 42 | 43 | let viewController = BadgeSelectorViewController( 44 | dependency: ( 45 | selectedBadgesModel: selectedBadgesModel, 46 | selectableBadgesModel: DefaultSelectableBadgesModel(dependency: ( 47 | allModel: allBadgesModel, 48 | selectedModel: selectedBadgesModel 49 | )) 50 | ) 51 | ) 52 | 53 | self.navigationController?.pushViewController(viewController, animated: true) 54 | } 55 | 56 | 57 | func goToHierarchalViewModelExampleScreen() { 58 | let viewController = QuizzesViewController(dependency: ( 59 | allQuizEntriesModel: DefaultAllQuizEntriesModel( 60 | gettingQuizEntriesVia: QuizEntriesDummyRepository() 61 | ), 62 | answerEntriesModel: DefaultAnswerEntriesModel(startingWith: [:]), 63 | quizIndexModel: DefaultQuizIndexModel(startingWith: 0) 64 | )) 65 | 66 | self.navigationController?.pushViewController(viewController, animated: true) 67 | } 68 | 69 | 70 | static func bootstrap(on window: UIWindow) -> DefaultCatalogWireframe { 71 | let navigationController = UINavigationController() 72 | 73 | window.rootViewController = navigationController 74 | window.makeKeyAndVisible() 75 | 76 | return DefaultCatalogWireframe(on: navigationController) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Entity/AnswerEntry.swift: -------------------------------------------------------------------------------- 1 | struct AnswerEntry: Hashable { 2 | let quizId: QuizEntry.Id 3 | let result: QuizResult 4 | 5 | 6 | var hashValue: Int { 7 | return self.quizId.number 8 | } 9 | 10 | 11 | static func ==(lhs: AnswerEntry, rhs: AnswerEntry) -> Bool { 12 | return lhs.quizId == rhs.quizId 13 | && lhs.result == rhs.result 14 | } 15 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Entity/Quiz.swift: -------------------------------------------------------------------------------- 1 | enum Quiz: Equatable { 2 | case addition(AdditionQuiz) 3 | case multiplication(MultiplicationQuiz) 4 | 5 | 6 | var question: String { 7 | switch self { 8 | case let .addition(quiz): 9 | return quiz.question 10 | case let .multiplication(quiz): 11 | return quiz.question 12 | } 13 | } 14 | 15 | 16 | func answer(value: Int) -> QuizResult { 17 | switch self { 18 | case let .addition(quiz): 19 | return quiz.answer(value: value) 20 | case let .multiplication(quiz): 21 | return quiz.answer(value: value) 22 | } 23 | } 24 | 25 | 26 | static func ==(lhs: Quiz, rhs: Quiz) -> Bool { 27 | switch (lhs, rhs) { 28 | case let (.addition(l), .addition(r)): 29 | return l == r 30 | case let (.multiplication(l), .multiplication(r)): 31 | return l == r 32 | default: 33 | return false 34 | } 35 | } 36 | } 37 | 38 | 39 | 40 | struct AdditionQuiz: Equatable { 41 | let value1: Int 42 | let value2: Int 43 | 44 | 45 | var question: String { 46 | return "\(self.value1) + \(self.value2) = ?" 47 | } 48 | 49 | 50 | func answer(value: Int) -> QuizResult { 51 | return self.value1 + self.value2 == value 52 | ? .correct 53 | : .wrong 54 | } 55 | 56 | 57 | static func ==(lhs: AdditionQuiz, rhs: AdditionQuiz) -> Bool { 58 | return lhs.value1 == rhs.value1 59 | && lhs.value2 == rhs.value2 60 | } 61 | } 62 | 63 | 64 | 65 | struct MultiplicationQuiz: Equatable { 66 | let value1: Int 67 | let value2: Int 68 | 69 | 70 | var question: String { 71 | return "\(self.value1) × \(self.value2) = ?" 72 | } 73 | 74 | 75 | func answer(value: Int) -> QuizResult { 76 | return self.value1 * self.value2 == value 77 | ? .correct 78 | : .wrong 79 | } 80 | 81 | 82 | static func ==(lhs: MultiplicationQuiz, rhs: MultiplicationQuiz) -> Bool { 83 | return lhs.value1 == rhs.value1 84 | && lhs.value2 == rhs.value2 85 | } 86 | } 87 | 88 | 89 | 90 | enum QuizResult: Equatable { 91 | case correct 92 | case wrong 93 | } 94 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Entity/QuizEntry.swift: -------------------------------------------------------------------------------- 1 | struct QuizEntry: Hashable { 2 | let id: Id 3 | let quiz: Quiz 4 | 5 | 6 | var hashValue: Int { 7 | return self.id.number 8 | } 9 | 10 | 11 | static func ==(lhs: QuizEntry, rhs: QuizEntry) -> Bool { 12 | return lhs.id == rhs.id 13 | && lhs.quiz == rhs.quiz 14 | } 15 | 16 | 17 | struct Id: Hashable { 18 | let number: Int 19 | 20 | 21 | var hashValue: Int { 22 | return self.number 23 | } 24 | 25 | 26 | static func ==(lhs: Id, rhs: Id) -> Bool { 27 | return lhs.number == rhs.number 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Models/AllQuizEntriesModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | protocol AllQuizEntriesModel { 7 | typealias State = EntityModelState 8 | 9 | var stateDidChange: RxCocoa.Driver { get } 10 | var currentState: State { get } 11 | } 12 | 13 | 14 | 15 | class DefaultAllQuizEntriesModel: AllQuizEntriesModel { 16 | private let entityModel: EntityModel 17 | 18 | 19 | let stateDidChange: RxCocoa.Driver 20 | 21 | 22 | var currentState: State { 23 | return self.entityModel.currentState 24 | } 25 | 26 | 27 | init( 28 | gettingQuizEntriesVia repository: Repository 29 | ) where Repository.P == Void, Repository.V == [QuizEntry.Id: QuizEntry], Repository.E == Never { 30 | self.entityModel = EntityModel( 31 | startingWith: .sleeping, 32 | gettingEntityBy: repository 33 | ) 34 | self.stateDidChange = entityModel.stateDidChange 35 | 36 | // NOTE: Kick first fetching. 37 | self.entityModel.get(by: ()) 38 | } 39 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Models/AnswerEntriesModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | protocol AnswerEntriesModel { 7 | var answersDidChange: RxCocoa.Driver<[QuizEntry.Id: AnswerEntry]> { get } 8 | var currentAnswers: [QuizEntry.Id: AnswerEntry] { get } 9 | 10 | func submit(quizId: QuizEntry.Id, result: QuizResult) 11 | } 12 | 13 | 14 | 15 | class DefaultAnswerEntriesModel: AnswerEntriesModel { 16 | private let stateMachine: StateMachine<[QuizEntry.Id: AnswerEntry]> 17 | 18 | 19 | let answersDidChange: Driver<[QuizEntry.Id: AnswerEntry]> 20 | 21 | 22 | var currentAnswers: [QuizEntry.Id: AnswerEntry] { 23 | get { return self.stateMachine.currentState } 24 | set { self.stateMachine.currentState = newValue } 25 | } 26 | 27 | 28 | init( 29 | startingWith initialAnswerEntries: [QuizEntry.Id: AnswerEntry] 30 | ) { 31 | self.stateMachine = StateMachine(startingWith: initialAnswerEntries) 32 | self.answersDidChange = stateMachine.stateDidChange 33 | } 34 | 35 | 36 | func submit(quizId: QuizEntry.Id, result: QuizResult) { 37 | let answerEntry = AnswerEntry( 38 | quizId: quizId, 39 | result: result 40 | ) 41 | 42 | var newAnswers = self.currentAnswers 43 | newAnswers[quizId] = answerEntry 44 | self.currentAnswers = newAnswers 45 | } 46 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Models/QuizIndexModel.swift: -------------------------------------------------------------------------------- 1 | import RxCocoa 2 | 3 | 4 | 5 | protocol QuizIndexModel { 6 | var indexDidChange: RxCocoa.Driver { get } 7 | var currentIndex: Int { get } 8 | 9 | func next() 10 | } 11 | 12 | 13 | 14 | class DefaultQuizIndexModel: QuizIndexModel { 15 | private let stateMachine: StateMachine 16 | 17 | 18 | let indexDidChange: RxCocoa.Driver 19 | 20 | 21 | var currentIndex: Int { 22 | return self.stateMachine.currentState 23 | } 24 | 25 | 26 | init(startingWith initialIndex: Int) { 27 | self.stateMachine = StateMachine(startingWith: initialIndex) 28 | self.indexDidChange = stateMachine.stateDidChange 29 | } 30 | 31 | 32 | func next() { 33 | self.stateMachine.currentState = self.stateMachine.currentState + 1 34 | } 35 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Repositories/QuizEntriesConstantRepository.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | 3 | 4 | 5 | class QuizEntriesConstantRepository: QuizEntriesRepository { 6 | private let quizEntries: [QuizEntry.Id: QuizEntry] 7 | 8 | 9 | init(returning quizEntries: [QuizEntry.Id: QuizEntry]) { 10 | self.quizEntries = quizEntries 11 | } 12 | 13 | 14 | func get(by parameters: Void) -> RxSwift.Single> { 15 | return .just(.success(self.quizEntries)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Repositories/QuizEntriesDummyRepository.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | 5 | 6 | class QuizEntriesDummyRepository: QuizEntriesRepository { 7 | private let constantRepository: QuizEntriesConstantRepository 8 | 9 | 10 | init() { 11 | self.constantRepository = QuizEntriesConstantRepository( 12 | returning: QuizEntriesDummyRepository.createRandomQuizEntries() 13 | ) 14 | } 15 | 16 | 17 | func get(by parameters: Void) -> Single> { 18 | return self.constantRepository.get(by: parameters) 19 | } 20 | 21 | 22 | private static func createRandomQuizEntries() -> [QuizEntry.Id: QuizEntry] { 23 | var dictionary = [QuizEntry.Id: QuizEntry]() 24 | 25 | (0..<5).forEach { idNumber in 26 | let id = QuizEntry.Id(number: idNumber) 27 | dictionary[id] = QuizEntry( 28 | id: id, 29 | quiz: QuizEntriesDummyRepository.createRandomQuiz() 30 | ) 31 | } 32 | 33 | return dictionary 34 | } 35 | 36 | 37 | private static func createRandomQuiz() -> Quiz { 38 | if arc4random() % 2 == 0 { 39 | return .addition(AdditionQuiz( 40 | value1: Int(arc4random() % 10), 41 | value2: Int(arc4random() % 10) 42 | )) 43 | } 44 | else { 45 | return .multiplication(MultiplicationQuiz( 46 | value1: Int(arc4random() % 10), 47 | value2: Int(arc4random() % 10) 48 | )) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Repositories/QuizEntriesRepository.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | 5 | 6 | protocol QuizEntriesRepository: EntityModelRepository { 7 | associatedtype V = [QuizEntry.Id: QuizEntry] 8 | associatedtype E = Never 9 | associatedtype P = Void 10 | } 11 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/ViewControllers/QuizzesViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxSwift 3 | import RxCocoa 4 | import RxDataSources 5 | 6 | 7 | 8 | class QuizzesViewController: UIViewController { 9 | typealias Dependency = ( 10 | allQuizEntriesModel: AllQuizEntriesModel, 11 | answerEntriesModel: AnswerEntriesModel, 12 | quizIndexModel: QuizIndexModel 13 | ) 14 | 15 | 16 | private let rootView: QuizzesRootView 17 | private var viewModel: QuizzesViewModel? 18 | private let dependency: Dependency 19 | private let disposeBag = RxSwift.DisposeBag() 20 | 21 | 22 | init(dependency: Dependency) { 23 | self.dependency = dependency 24 | 25 | self.rootView = QuizzesRootView() 26 | 27 | super.init(nibName: nil, bundle: nil) 28 | } 29 | 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | return nil 33 | } 34 | 35 | 36 | override func loadView() { 37 | self.view = self.rootView 38 | 39 | self.title = "Quiz" 40 | } 41 | 42 | 43 | override func viewDidLoad() { 44 | super.viewDidLoad() 45 | 46 | let viewModel = QuizzesViewModel( 47 | dependency: ( 48 | allQuizEntriesModel: self.dependency.allQuizEntriesModel, 49 | answerEntriesModel: self.dependency.answerEntriesModel, 50 | quizIndexModel: self.dependency.quizIndexModel 51 | ) 52 | ) 53 | // NOTE: Prevent removing by ARC. 54 | self.viewModel = viewModel 55 | 56 | let dataSource = RxDataSources.RxCollectionViewSectionedReloadDataSource>( 57 | configureCell: { dataSource, collectionView, indexPath, item in 58 | return QuizCell.dequeue(from: collectionView, for: indexPath, parentViewModel: viewModel) 59 | }, 60 | configureSupplementaryView: { dataSource, collectionView, string, indexPath in 61 | return UICollectionViewCell() 62 | } 63 | ) 64 | 65 | viewModel.quizEntries 66 | .map { quizzes in [RxDataSources.SectionModel(model: "Quiz", items: quizzes)] } 67 | .drive(self.rootView.collectionOutlet.rx.items(dataSource: dataSource)) 68 | .disposed(by: self.disposeBag) 69 | } 70 | 71 | 72 | override func viewDidAppear(_ animated: Bool) { 73 | self.viewModel?.currentQuizIndex 74 | .drive(onNext: { [weak self] index in 75 | guard let `self` = self else { return } 76 | guard index < self.rootView.collectionOutlet.numberOfItems(inSection: 0) else { return } 77 | let indexPath = IndexPath(row: index, section: 0) 78 | 79 | self.rootView.collectionOutlet.scrollToItem( 80 | at: indexPath, 81 | at: .centeredHorizontally, 82 | animated: true 83 | ) 84 | 85 | Timer.scheduledTimer( 86 | withTimeInterval: 0.8, 87 | repeats: false, 88 | block: { _ in 89 | if let cell = self.rootView.collectionOutlet.cellForItem(at: indexPath) as? QuizCell { 90 | cell.answerOutlet.becomeFirstResponder() 91 | } 92 | } 93 | ) 94 | }) 95 | .disposed(by: self.disposeBag) 96 | } 97 | } 98 | 99 | 100 | 101 | extension QuizEntry: RxDataSources.IdentifiableType { 102 | typealias Identity = QuizEntry.Id 103 | 104 | 105 | var identity: Identity { 106 | return self.id 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/ViewModels/QuizViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class QuizViewModel { 7 | typealias Input = ( 8 | answerText: RxCocoa.Driver, 9 | answerDone: RxCocoa.Signal, 10 | submitTap: RxCocoa.Signal, 11 | resultShown: RxCocoa.Signal 12 | ) 13 | let quiz: RxCocoa.Driver 14 | let submittedQuizResult: RxCocoa.Signal 15 | let canEdit: RxCocoa.Driver 16 | let canSubmit: RxCocoa.Driver 17 | let shownQuizResult: RxCocoa.Signal 18 | 19 | private let disposeBag = RxSwift.DisposeBag() 20 | 21 | 22 | init( 23 | input: Input, 24 | dependency quiz: Quiz 25 | ) { 26 | self.quiz = RxCocoa.Driver.just(quiz) 27 | 28 | let answerRelay = RxCocoa.BehaviorRelay(value: nil) 29 | let answer = answerRelay.asDriver() 30 | let quizResultRelay = RxCocoa.BehaviorRelay(value: nil) 31 | let quizResult = quizResultRelay.asDriver() 32 | 33 | self.submittedQuizResult = RxCocoa.Signal 34 | .merge( 35 | input.answerDone, 36 | input.submitTap 37 | ) 38 | .flatMap { answer -> RxCocoa.Signal in 39 | guard let answer = answerRelay.value else { return .empty() } 40 | return .just(quiz.answer(value: answer)) 41 | } 42 | .asSignal(onErrorSignalWith: .empty()) 43 | 44 | self.shownQuizResult = input.resultShown 45 | .flatMap { _ -> RxCocoa.Driver in 46 | guard let quizResult = quizResultRelay.value else { return .empty() } 47 | return .just(quizResult) 48 | } 49 | .asSignal(onErrorSignalWith: .empty()) 50 | 51 | self.canEdit = quizResult 52 | .map { quizResult in quizResult == nil } 53 | .asDriver() 54 | 55 | self.canSubmit = answer 56 | .map { answer in answer != nil } 57 | .asDriver() 58 | 59 | self.submittedQuizResult 60 | .emit(onNext: { quizResult in 61 | quizResultRelay.accept(quizResult) 62 | }) 63 | .disposed(by: self.disposeBag) 64 | 65 | input.answerText 66 | .map { answerText -> Int? in 67 | guard let answerText = answerText else { return nil } 68 | return Int(answerText) 69 | } 70 | .drive(answerRelay) 71 | .disposed(by: self.disposeBag) 72 | } 73 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/ViewModels/QuizzesViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class QuizzesViewModel { 7 | typealias Dependency = ( 8 | allQuizEntriesModel: AllQuizEntriesModel, 9 | answerEntriesModel: AnswerEntriesModel, 10 | quizIndexModel: QuizIndexModel 11 | ) 12 | 13 | let quizEntries: RxCocoa.Driver<[QuizEntry]> 14 | let currentQuizIndex: RxCocoa.Driver 15 | 16 | private var childViewModels: [ObjectIdentifier: (viewModel: QuizViewModel, disposeBag: RxSwift.DisposeBag)] = [:] 17 | private let quizResultAggregateRelay = RxCocoa.PublishRelay<(quizId: QuizEntry.Id, result: QuizResult)>() 18 | 19 | private let dependency: Dependency 20 | private let disposeBag = RxSwift.DisposeBag() 21 | 22 | 23 | init(dependency: Dependency) { 24 | self.dependency = dependency 25 | 26 | self.currentQuizIndex = dependency.quizIndexModel 27 | .indexDidChange 28 | 29 | self.quizEntries = dependency.allQuizEntriesModel 30 | .stateDidChange 31 | .map { state -> [QuizEntry] in QuizzesViewModel.createQuizEntrySequence(from: state.value ?? [:]) } 32 | .asDriver() 33 | 34 | self.quizResultAggregateRelay 35 | .asSignal() 36 | .emit(onNext: { [weak self] quizIdAndQuizResult in 37 | guard let `self` = self else { return } 38 | 39 | self.dependency.answerEntriesModel.submit( 40 | quizId: quizIdAndQuizResult.quizId, 41 | result: quizIdAndQuizResult.result 42 | ) 43 | self.dependency.quizIndexModel.next() 44 | }) 45 | .disposed(by: self.disposeBag) 46 | } 47 | 48 | 49 | static func createQuizEntrySequence(from quizEntriesSet: [QuizEntry.Id: QuizEntry]) -> [QuizEntry] { 50 | return quizEntriesSet 51 | .sorted { $0.key.number > $1.key.number } 52 | .map { entry in entry.value } 53 | } 54 | } 55 | 56 | 57 | 58 | // These methods is for dynamic hierarchal ViewModels. 59 | extension QuizzesViewModel { 60 | /// Create a new child ViewModel and start subscribing events from the child ViewModel. 61 | func createChildViewModel(input: QuizViewModel.Input, at index: Int) -> QuizViewModel? { 62 | let quizEntriesSet = self.dependency.allQuizEntriesModel.currentState.value ?? [:] 63 | let quizEntries = QuizzesViewModel.createQuizEntrySequence(from: quizEntriesSet) 64 | guard index < quizEntries.count else { return nil } 65 | 66 | let quizEntry = quizEntries[index] 67 | 68 | let childViewModel = QuizViewModel( 69 | input: input, 70 | dependency: quizEntry.quiz 71 | ) 72 | 73 | self.addChildViewModel(childViewModel, for: quizEntry.id) 74 | 75 | return childViewModel 76 | } 77 | 78 | 79 | /// Start subscribing events from the child ViewModel. 80 | func addChildViewModel(_ childViewModel: QuizViewModel, for quizId: QuizEntry.Id) { 81 | let disposeBag = RxSwift.DisposeBag() 82 | 83 | childViewModel 84 | .shownQuizResult 85 | .map { result in (quizId: quizId, result: result) } 86 | .emit(to: self.quizResultAggregateRelay) 87 | .disposed(by: disposeBag) 88 | 89 | self.childViewModels[ObjectIdentifier(childViewModel)] = ( 90 | viewModel: childViewModel, 91 | disposeBag: disposeBag 92 | ) 93 | } 94 | 95 | 96 | /// Remove the specified child ViewModel and unsubscribe events from the child ViewModel. 97 | func removeChildViewModel(_ childViewModel: QuizViewModel) { 98 | // NOTE: It will trigger DisposeBag removal, so subscriptions of 99 | // child ViewModels will be removed together. 100 | self.childViewModels[ObjectIdentifier(childViewModel)] = nil 101 | } 102 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Views/QuizCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxSwift 3 | import RxCocoa 4 | 5 | 6 | 7 | class QuizCell: UICollectionViewCell { 8 | @IBOutlet weak var answerOutlet: UITextField! 9 | @IBOutlet weak var questionOutlet: UILabel! 10 | @IBOutlet weak var submitOutlet: UIButton! 11 | @IBOutlet weak var resultOutlet: UILabel! 12 | 13 | 14 | private var resultShownRelay: RxCocoa.PublishRelay? 15 | private var disposeBag: RxSwift.DisposeBag? 16 | private var viewModel: QuizViewModel? 17 | 18 | 19 | private func decorate() { 20 | self.layer.shadowRadius = 2 21 | self.layer.shadowOffset = CGSize(width: 0, height: 2) 22 | self.layer.shadowOpacity = 0.3 23 | self.layer.shadowColor = UIColor.black.cgColor 24 | self.layer.masksToBounds = false 25 | 26 | self.submitOutlet.isHidden = false 27 | self.submitOutlet.alpha = 1 28 | self.resultOutlet.isHidden = true 29 | self.resultOutlet.alpha = 0 30 | } 31 | 32 | 33 | private static let reuseIdentifier = "QuizCell" 34 | private static let nibName = "QuizCell" 35 | 36 | 37 | static func register(to collectionView: UICollectionView) { 38 | let nib = UINib(nibName: self.nibName, bundle: Bundle(for: self)) 39 | collectionView.register(nib, forCellWithReuseIdentifier: self.reuseIdentifier) 40 | } 41 | 42 | 43 | static func dequeue( 44 | from collectionView: UICollectionView, 45 | for indexPath: IndexPath, 46 | parentViewModel: QuizzesViewModel 47 | ) -> UICollectionViewCell { 48 | guard let cell = collectionView.dequeueReusableCell( 49 | withReuseIdentifier: self.reuseIdentifier, 50 | for: indexPath 51 | ) as? QuizCell else { 52 | fatalError("Please register \(self.reuseIdentifier)") 53 | } 54 | 55 | let resultShownRelay = RxCocoa.PublishRelay() 56 | 57 | guard let viewModel = cell.swapViewModel( 58 | input: ( 59 | answerText: cell.answerOutlet.rx 60 | .text 61 | .asDriver(), 62 | 63 | answerDone: cell.answerOutlet.rx 64 | .controlEvent(.editingDidEndOnExit) 65 | .asSignal(onErrorSignalWith: .empty()), 66 | 67 | submitTap: cell.submitOutlet.rx 68 | .tap 69 | .asSignal(onErrorSignalWith: .empty()), 70 | 71 | resultShown: resultShownRelay 72 | .asSignal() 73 | ), 74 | for: indexPath, 75 | willAddChildViewModelTo: parentViewModel 76 | ) else { 77 | fatalError("Unavailable indexPath: \(indexPath)") 78 | } 79 | 80 | // NOTE: Prevent removing by ARC. 81 | cell.viewModel = viewModel 82 | 83 | // NOTE: Unsubscribe all previous ViewModels subscriptions. 84 | let disposeBag = RxSwift.DisposeBag() 85 | cell.disposeBag = disposeBag 86 | cell.resultShownRelay = resultShownRelay 87 | cell.answerOutlet.text = nil 88 | 89 | viewModel.quiz 90 | .map { quiz in quiz.question } 91 | .drive(cell.questionOutlet.rx.text) 92 | .disposed(by: disposeBag) 93 | 94 | viewModel.canSubmit 95 | .drive(cell.submitOutlet.rx.isEnabled) 96 | .disposed(by: disposeBag) 97 | 98 | viewModel.canEdit 99 | .drive(cell.answerOutlet.rx.isEnabled) 100 | .disposed(by: disposeBag) 101 | 102 | viewModel.submittedQuizResult 103 | .emit(onNext: { [weak cell] result in 104 | guard let cell = cell else { 105 | return 106 | } 107 | cell.showResult(result) 108 | }) 109 | .disposed(by: disposeBag) 110 | 111 | cell.decorate() 112 | 113 | return cell 114 | } 115 | 116 | 117 | private func showResult(_ result: QuizResult) { 118 | self.submitOutlet.alpha = 1 119 | 120 | switch result { 121 | case .correct: 122 | self.resultOutlet.textColor = .green 123 | self.resultOutlet.text = "Correct!" 124 | case .wrong: 125 | self.resultOutlet.textColor = .red 126 | self.resultOutlet.text = "Wrong!" 127 | } 128 | 129 | UIView.animate( 130 | withDuration: 0.15, 131 | animations: { 132 | self.submitOutlet.alpha = 0 133 | }, 134 | completion: { _ in 135 | self.submitOutlet.isHidden = true 136 | 137 | self.resultOutlet.alpha = 0 138 | self.resultOutlet.isHidden = false 139 | 140 | UIView.animate( 141 | withDuration: 0.15, 142 | animations: { 143 | self.resultOutlet.alpha = 1 144 | }, 145 | completion: { _ in 146 | Timer.scheduledTimer( 147 | withTimeInterval: 1, 148 | repeats: false, 149 | block: { _ in 150 | self.resultShownRelay?.accept(()) 151 | } 152 | ) 153 | } 154 | ) 155 | } 156 | ) 157 | } 158 | 159 | 160 | private func swapViewModel( 161 | input: QuizViewModel.Input, 162 | for indexPath: IndexPath, 163 | willAddChildViewModelTo parentViewModel: QuizzesViewModel 164 | ) -> QuizViewModel? { 165 | if let previousViewModel = self.viewModel { 166 | parentViewModel.removeChildViewModel(previousViewModel) 167 | } 168 | 169 | return parentViewModel.createChildViewModel( 170 | input: input, 171 | at: indexPath.row 172 | ) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Views/QuizzesRootView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | class QuizzesRootView: UIView { 5 | @IBOutlet weak var collectionOutlet: UICollectionView! { 6 | didSet { 7 | let margin: CGFloat = 20 8 | let navigationHeight: CGFloat = 44 9 | 10 | let itemWidth = UIScreen.main.bounds.width 11 | - margin * 2 12 | 13 | let itemHeight = UIScreen.main.bounds.height 14 | - UIApplication.shared.statusBarFrame.height 15 | - navigationHeight 16 | - margin * 2 17 | 18 | let layout = UICollectionViewFlowLayout() 19 | layout.scrollDirection = .horizontal 20 | layout.itemSize = CGSize(width: itemWidth, height: itemHeight) 21 | layout.minimumInteritemSpacing = margin / 2 22 | layout.sectionInset = UIEdgeInsets( 23 | top: margin, 24 | left: margin, 25 | bottom: margin, 26 | right: margin 27 | ) 28 | self.collectionOutlet.collectionViewLayout = layout 29 | QuizCell.register(to: self.collectionOutlet) 30 | } 31 | } 32 | 33 | 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | self.setUp() 37 | } 38 | 39 | 40 | required init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | self.setUp() 43 | } 44 | 45 | 46 | private func setUp() { 47 | guard let view = QuizzesRootView.loadViewFromXib(owner: self) else { return } 48 | 49 | view.translatesAutoresizingMaskIntoConstraints = false 50 | self.addSubview(view) 51 | 52 | FilledLayout.fill(subview: view, into: self) 53 | } 54 | 55 | 56 | private static let nibName = "QuizzesScreen" 57 | 58 | 59 | private static func loadViewFromXib(owner: QuizzesRootView) -> UIView? { 60 | let nib = UINib(nibName: self.nibName, bundle: Bundle(for: self)) 61 | return nib.instantiate(withOwner: owner).first as? UIView 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Xibs/QuizCell.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 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /RxNextExample/Examples/DynamicHierarchalViewModelExample/Xibs/QuizzesScreen.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 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectedBadgesScreen/ViewControllers/SelectedBadgesViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxSwift 3 | import RxCocoa 4 | import RxDataSources 5 | 6 | 7 | 8 | class SelectedBadgesViewController: UIViewController { 9 | private let selectedBadgesModel: SelectedBadgesModel 10 | 11 | private let rootView: SelectedBadgesRootView 12 | private let backButton: UIBarButtonItem 13 | 14 | private var viewModel: SelectedBadgesScreenViewModel? 15 | private let disposeBag = RxSwift.DisposeBag() 16 | 17 | 18 | init(dependency selectedBadgesModel: SelectedBadgesModel) { 19 | self.selectedBadgesModel = selectedBadgesModel 20 | 21 | self.rootView = SelectedBadgesRootView() 22 | self.backButton = UIBarButtonItem( 23 | title: "Back", 24 | style: .plain, 25 | target: nil, 26 | action: nil 27 | ) 28 | 29 | super.init(nibName: nil, bundle: nil) 30 | } 31 | 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | return nil 35 | } 36 | 37 | 38 | override func loadView() { 39 | self.view = self.rootView 40 | 41 | self.title = "Selected Badges" 42 | 43 | self.navigationItem.leftBarButtonItem = self.backButton 44 | } 45 | 46 | 47 | override func viewDidLoad() { 48 | let viewModel = SelectedBadgesScreenViewModel( 49 | input: self.backButton.rx 50 | .tap 51 | .asSignal(onErrorSignalWith: .empty()), 52 | 53 | dependency: ( 54 | selectedModel: self.selectedBadgesModel, 55 | wireframe: DefaultPresentedViewControllerWireframe(willDismiss: self) 56 | ) 57 | ) 58 | // NOTE: Prevent removing by ARC. 59 | self.viewModel = viewModel 60 | 61 | let selectedDataSource = RxDataSources.RxCollectionViewSectionedAnimatedDataSource>( 62 | configureCell: { dataSource, collectionView, indexPath, badge in 63 | return SelectedBadgeCell.dequeue(from: collectionView, for: indexPath, badge: badge) 64 | }, 65 | configureSupplementaryView: { dataSource, collectionView, string, indexPath in 66 | return UICollectionViewCell() 67 | } 68 | ) 69 | 70 | viewModel.selectedViewModel.selectedBadges 71 | .map { badges in [RxDataSources.AnimatableSectionModel(model: "Selected", items: badges)] } 72 | .drive(self.rootView.selectedCollectionOutlet.rx.items(dataSource: selectedDataSource)) 73 | .disposed(by: self.disposeBag) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectedBadgesScreen/ViewModels/ReadOnlySelectedBadgesViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxCocoa 2 | 3 | 4 | 5 | class ReadOnlySelectedBadgesViewModel { 6 | let selectedBadges: RxCocoa.Driver<[Badge]> 7 | 8 | private let selectedModel: SelectedBadgesModel 9 | 10 | 11 | init(dependency selectedModel: SelectedBadgesModel) { 12 | self.selectedModel = selectedModel 13 | self.selectedBadges = selectedModel.selectionDidChange 14 | } 15 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectedBadgesScreen/ViewModels/SelectedBadgesScreenViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | class SelectedBadgesScreenViewModel { 6 | typealias Dependency = ( 7 | selectedModel: SelectedBadgesModel, 8 | wireframe: PresentedViewControllerWireframe 9 | ) 10 | let selectedViewModel: ReadOnlySelectedBadgesViewModel 11 | 12 | private let dependency: Dependency 13 | private let disposeBag = RxSwift.DisposeBag() 14 | 15 | 16 | init( 17 | input backTap: RxCocoa.Signal, 18 | dependency: Dependency 19 | ) { 20 | self.dependency = dependency 21 | self.selectedViewModel = ReadOnlySelectedBadgesViewModel( 22 | dependency: dependency.selectedModel 23 | ) 24 | 25 | backTap 26 | .emit(onNext: { [weak self] _ in 27 | guard let `self` = self else { return } 28 | self.dependency.wireframe.goBack() 29 | }) 30 | .disposed(by: self.disposeBag) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectedBadgesScreen/Views/SelectedBadgesRootView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | class SelectedBadgesRootView: UIView { 5 | @IBOutlet weak var selectedCollectionOutlet: UICollectionView! { 6 | didSet { 7 | let layout = UICollectionViewFlowLayout() 8 | layout.scrollDirection = .vertical 9 | layout.itemSize = CGSize(width: 80, height: 80) 10 | layout.minimumLineSpacing = 10 11 | layout.minimumInteritemSpacing = 10 12 | layout.sectionInset = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) 13 | self.selectedCollectionOutlet.collectionViewLayout = layout 14 | SelectedBadgeCell.register(to: self.selectedCollectionOutlet) 15 | } 16 | } 17 | 18 | 19 | override init(frame: CGRect) { 20 | super.init(frame: frame) 21 | self.setUp() 22 | } 23 | 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | super.init(coder: aDecoder) 27 | self.setUp() 28 | } 29 | 30 | 31 | private func setUp() { 32 | guard let view = SelectedBadgesRootView.loadViewFromXib(owner: self) else { return } 33 | 34 | view.translatesAutoresizingMaskIntoConstraints = false 35 | self.addSubview(view) 36 | 37 | FilledLayout.fill(subview: view, into: self) 38 | } 39 | 40 | 41 | private static let nibName = "SelectedBadgesScreen" 42 | 43 | 44 | private static func loadViewFromXib(owner: SelectedBadgesRootView) -> UIView? { 45 | let nib = UINib(nibName: self.nibName, bundle: Bundle(for: self)) 46 | return nib.instantiate(withOwner: owner).first as? UIView 47 | } 48 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectedBadgesScreen/Xibs/SelectedBadgesScreen.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 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Models/AllBadgesModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | protocol AllBadgesModel: class { 7 | typealias State = EntityModelState 8 | 9 | var stateDidChange: RxCocoa.Driver { get } 10 | var currentState: State { get } 11 | } 12 | 13 | 14 | 15 | class DefaultAllBadgesModel: AllBadgesModel { 16 | private let entityModel: EntityModel 17 | private let disposeBag = RxSwift.DisposeBag() 18 | 19 | 20 | let stateDidChange: RxCocoa.Driver 21 | 22 | 23 | var currentState: State { 24 | return self.entityModel.currentState 25 | } 26 | 27 | 28 | init( 29 | gettingBadgesVia repository: Repository 30 | ) where Repository.P == Void, Repository.V == [Badge], Repository.E == Never { 31 | self.entityModel = EntityModel( 32 | startingWith: .sleeping, 33 | gettingEntityBy: repository 34 | ) 35 | self.stateDidChange = entityModel.stateDidChange 36 | 37 | self.entityModel.get(by: ()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Models/SelectableBadgesModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | protocol SelectableBadgesModel { 7 | var selectableBadgesDidChange: RxCocoa.Driver<[Badge]> { get } 8 | var currentSelectableBadges: [Badge] { get } 9 | } 10 | 11 | 12 | 13 | class DefaultSelectableBadgesModel: SelectableBadgesModel { 14 | typealias Dependency = ( 15 | allModel: AllBadgesModel, 16 | selectedModel: SelectedBadgesModel 17 | ) 18 | private let disposeBag = RxSwift.DisposeBag() 19 | private let dependency: Dependency 20 | private let selectableBadgesRelay: RxCocoa.BehaviorRelay<[Badge]> 21 | 22 | 23 | let selectableBadgesDidChange: RxCocoa.Driver<[Badge]> 24 | 25 | 26 | var currentSelectableBadges: [Badge] { 27 | return selectableBadgesRelay.value 28 | } 29 | 30 | 31 | init(dependency: Dependency) { 32 | self.dependency = dependency 33 | 34 | // NOTE: Use a BehaviorRelay because this model has a synchronous getter for 35 | // selected badges. 36 | let selectableBadgesRelay = RxCocoa.BehaviorRelay( 37 | value: DefaultSelectableBadgesModel.dropSelected( 38 | from: dependency.allModel.currentState.value ?? [], 39 | without: Set(dependency.selectedModel.currentSelection) 40 | ) 41 | ) 42 | self.selectableBadgesRelay = selectableBadgesRelay 43 | self.selectableBadgesDidChange = selectableBadgesRelay.asDriver() 44 | 45 | RxCocoa.Driver 46 | .combineLatest( 47 | dependency.allModel.stateDidChange, 48 | dependency.selectedModel.selectionDidChange, 49 | resultSelector: { ($0, $1) } 50 | ) 51 | .map { tuple -> [Badge] in 52 | let (allBadgesModelState, selectedBadges) = tuple 53 | return DefaultSelectableBadgesModel.dropSelected( 54 | from: allBadgesModelState.value ?? [], 55 | without: Set(selectedBadges) 56 | ) 57 | } 58 | .drive(self.selectableBadgesRelay) 59 | .disposed(by: self.disposeBag) 60 | } 61 | 62 | 63 | private static func dropSelected(from allBadges: [Badge], without selectedBadges: Set) -> [Badge] { 64 | return allBadges 65 | .filter { !selectedBadges.contains($0) } 66 | } 67 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Repositories/BadgesConstantRepository.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | 3 | 4 | 5 | class BadgesConstantRepository: BadgesRepository { 6 | private let badges: [Badge] 7 | 8 | 9 | init(returning badges: [Badge]) { 10 | self.badges = badges 11 | } 12 | 13 | 14 | func get(by parameters: Void) -> RxSwift.Single> { 15 | return .just(.success(self.badges)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Repositories/BadgesDummyRepository.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxSwift 3 | 4 | 5 | 6 | class BadgesDummyRepository: BadgesRepository { 7 | private let constantRepository: BadgesConstantRepository 8 | 9 | 10 | init() { 11 | self.constantRepository = BadgesConstantRepository( 12 | returning: (0..<100).map { id in 13 | let color = BadgesDummyRepository.generateRandomBadgeColor() 14 | return Badge( 15 | id: id, 16 | title: color.hex, 17 | color: color.value 18 | ) 19 | } 20 | ) 21 | } 22 | 23 | 24 | func get(by parameters: Void) -> Single> { 25 | return self.constantRepository.get(by: parameters) 26 | } 27 | 28 | 29 | private static func generateBadgeColor(red: CGFloat, green: CGFloat, blue: CGFloat) -> (hex: String, value: UIColor) { 30 | return ( 31 | hex: String( 32 | format: "#%02X%02X%02X", 33 | arguments: [ 34 | Int(red * 256), 35 | Int(green * 256), 36 | Int(blue * 256), 37 | ] 38 | ), 39 | value: UIColor( 40 | red: red, 41 | green: green, 42 | blue: blue, 43 | alpha: 1 44 | ) 45 | ) 46 | } 47 | 48 | 49 | private static func generateRandomBadgeColor() -> (hex: String, value: UIColor) { 50 | return self.generateBadgeColor( 51 | red: CGFloat(arc4random() % 100) / 100, 52 | green: CGFloat(arc4random() % 100) / 100, 53 | blue: CGFloat(arc4random() % 100) / 100 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Repositories/BadgesRepository.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | 3 | 4 | 5 | protocol BadgesRepository: EntityModelRepository { 6 | associatedtype V = [Badge] 7 | associatedtype E = Never 8 | associatedtype P = Void 9 | } 10 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/ViewControllers/BadgeSelectorViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxSwift 3 | import RxCocoa 4 | import RxDataSources 5 | 6 | 7 | 8 | class BadgeSelectorViewController: UIViewController { 9 | typealias Dependency = ( 10 | selectedBadgesModel: SelectedBadgesModel, 11 | selectableBadgesModel: SelectableBadgesModel 12 | ) 13 | private let dependency: Dependency 14 | private let rootView: BadgeSelectorRootView 15 | private let doneButton: UIBarButtonItem 16 | private var viewModel: BadgesSelectorViewModel? 17 | private let disposeBag = RxSwift.DisposeBag() 18 | 19 | 20 | init(dependency: Dependency) { 21 | self.dependency = dependency 22 | 23 | self.rootView = BadgeSelectorRootView() 24 | self.doneButton = UIBarButtonItem( 25 | barButtonSystemItem: .done, 26 | target: nil, 27 | action: nil 28 | ) 29 | 30 | super.init(nibName: nil, bundle: nil) 31 | } 32 | 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | return nil 36 | } 37 | 38 | 39 | override func loadView() { 40 | self.view = self.rootView 41 | 42 | self.navigationItem.rightBarButtonItem = self.doneButton 43 | 44 | self.title = "Select Badges" 45 | } 46 | 47 | 48 | override func viewDidLoad() { 49 | let viewModel = BadgesSelectorViewModel( 50 | input: ( 51 | doneTap: self.doneButton.rx 52 | .tap 53 | .asSignal(onErrorSignalWith: .empty()), 54 | 55 | selectedTap: self.rootView.selectedCollectionOutlet.rx 56 | .modelSelected(Badge.self) 57 | .asSignal(onErrorSignalWith: .empty()), 58 | 59 | selectableTap: self.rootView.selectableCollectionOutlet.rx 60 | .modelSelected(Badge.self) 61 | .asSignal(onErrorSignalWith: .empty()) 62 | ), 63 | dependency: ( 64 | selectedModel: self.dependency.selectedBadgesModel, 65 | selectableModel: self.dependency.selectableBadgesModel, 66 | wireframe: DefaultBadgeSelectorWireframe(on: self) 67 | ) 68 | ) 69 | // NOTE: Prevent removing by ARC. 70 | self.viewModel = viewModel 71 | 72 | let selectedDataSource = RxCollectionViewSectionedAnimatedDataSource>( 73 | configureCell: { dataSource, collectionView, indexPath, badge in 74 | return SelectedBadgeCell.dequeue(from: collectionView, for: indexPath, badge: badge) 75 | }, 76 | configureSupplementaryView: { dataSource, collectionView, string, indexPath in 77 | return UICollectionViewCell() 78 | } 79 | ) 80 | 81 | viewModel.selectedViewModel.selectedBadges 82 | .map { badges in [RxDataSources.AnimatableSectionModel(model: "Selected", items: badges)] } 83 | .drive(self.rootView.selectedCollectionOutlet.rx.items(dataSource: selectedDataSource)) 84 | .disposed(by: self.disposeBag) 85 | 86 | viewModel.selectedViewModel.badgeDidSelect 87 | .emit(onNext: { [weak self] badge in 88 | guard let `self` = self else { return } 89 | 90 | DispatchQueue.main.async { 91 | self.scroll(to: badge) 92 | } 93 | }) 94 | .disposed(by: self.disposeBag) 95 | 96 | let selectableDataSource = RxCollectionViewSectionedAnimatedDataSource>( 97 | configureCell: { dataSource, collectionView, indexPath, badge in 98 | return SelectableBadgeCell.dequeue(from: collectionView, for: indexPath, badge: badge) 99 | }, 100 | configureSupplementaryView: { dataSource, collectionView, string, indexPath in 101 | return UICollectionViewCell() 102 | } 103 | ) 104 | 105 | viewModel.selectableViewModel.selectableBadges 106 | .map { badges in [RxDataSources.AnimatableSectionModel(model: "Selectable", items: badges)] } 107 | .drive(self.rootView.selectableCollectionOutlet.rx.items(dataSource: selectableDataSource)) 108 | .disposed(by: self.disposeBag) 109 | 110 | viewModel.completionViewModel 111 | .canComplete 112 | .drive(self.doneButton.rx.isEnabled) 113 | .disposed(by: self.disposeBag) 114 | } 115 | 116 | 117 | private func scroll(to badge: Badge) { 118 | guard let index = self.dependency.selectedBadgesModel.currentSelection.index(of: badge), 119 | self.isAvailableIndex(index) else { return } 120 | 121 | self.rootView.selectedCollectionOutlet.scrollToItem( 122 | at: IndexPath(row: index, section: 0), 123 | at: .centeredHorizontally, 124 | animated: true 125 | ) 126 | } 127 | 128 | 129 | // XXX: This is a workaround for animated cell changes. 130 | // It is necessary because we cannot detect when the new cell is added. 131 | private func isAvailableIndex(_ index: Int) -> Bool { 132 | guard let dataSource = self.rootView.selectedCollectionOutlet.dataSource else { 133 | return false 134 | } 135 | 136 | let numberOfItems = dataSource.collectionView(self.rootView.selectedCollectionOutlet, numberOfItemsInSection: 0) 137 | return index < numberOfItems 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/ViewModels/BadgeSelectorCompletionViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class BadgeSelectorCompletionViewModel { 7 | typealias Dependency = ( 8 | selectedModel: SelectedBadgesModel, 9 | wireframe: BadgeSelectorWireframe 10 | ) 11 | 12 | let canComplete: RxCocoa.Driver 13 | 14 | 15 | private let dependency: Dependency 16 | private let disposeBag = DisposeBag() 17 | 18 | 19 | init( 20 | input doneTap: RxCocoa.Signal, 21 | dependency: Dependency 22 | ) { 23 | self.dependency = dependency 24 | 25 | self.canComplete = dependency.selectedModel 26 | .selectionDidChange 27 | .map { selection in !selection.isEmpty } 28 | .asDriver() 29 | 30 | doneTap 31 | .emit(onNext: { [weak self] _ in 32 | guard let `self` = self else { return } 33 | 34 | self.dependency.wireframe.goToResultScreen( 35 | with: self.dependency.selectedModel 36 | ) 37 | }) 38 | .disposed(by: self.disposeBag) 39 | } 40 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/ViewModels/BadgeSelectorViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class BadgesSelectorViewModel { 7 | typealias Dependency = ( 8 | selectedModel: SelectedBadgesModel, 9 | selectableModel: SelectableBadgesModel, 10 | wireframe: BadgeSelectorWireframe 11 | ) 12 | typealias Input = ( 13 | doneTap: RxCocoa.Signal, 14 | selectedTap: RxCocoa.Signal, 15 | selectableTap: RxCocoa.Signal 16 | ) 17 | 18 | // There are Child ViewModels. 19 | let selectedViewModel: SelectedBadgesViewModel 20 | let selectableViewModel: SelectableBadgesViewModel 21 | let completionViewModel: BadgeSelectorCompletionViewModel 22 | 23 | private let disposeBag = RxSwift.DisposeBag() 24 | 25 | 26 | init(input: Input, dependency: Dependency) { 27 | // This is a Model shared between 2 ViewModels. 28 | let selectedModel = dependency.selectedModel 29 | 30 | self.selectedViewModel = SelectedBadgesViewModel( 31 | input: input.selectedTap, 32 | dependency: selectedModel // Sharing the Model. 33 | ) 34 | 35 | self.selectableViewModel = SelectableBadgesViewModel( 36 | input: input.selectableTap, 37 | dependency: ( 38 | selectedModel: selectedModel, // Sharing the Model. 39 | selectableModel: dependency.selectableModel 40 | ) 41 | ) 42 | 43 | self.completionViewModel = BadgeSelectorCompletionViewModel( 44 | input: input.doneTap, 45 | dependency: ( 46 | selectedModel: selectedModel, // Sharing the Model. 47 | wireframe: dependency.wireframe 48 | ) 49 | ) 50 | } 51 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/ViewModels/SelectableBadgesViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class SelectableBadgesViewModel { 7 | typealias Dependency = ( 8 | selectedModel: SelectedBadgesModel, 9 | selectableModel: SelectableBadgesModel 10 | ) 11 | let selectableBadges: RxCocoa.Driver<[Badge]> 12 | 13 | private let dependency: Dependency 14 | private let disposeBag = RxSwift.DisposeBag() 15 | 16 | 17 | init( 18 | input selectableTap: RxCocoa.Signal, 19 | dependency: Dependency 20 | ) { 21 | self.dependency = dependency 22 | self.selectableBadges = dependency.selectableModel 23 | .selectableBadgesDidChange 24 | 25 | selectableTap 26 | .emit(onNext: { [weak self] badge in 27 | guard let `self` = self else { return } 28 | 29 | self.dependency.selectedModel.select(badge: badge) 30 | }) 31 | .disposed(by: self.disposeBag) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/ViewModels/SelectedBadgesViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class SelectedBadgesViewModel { 7 | let selectedBadges: RxCocoa.Driver<[Badge]> 8 | let badgeDidSelect: RxCocoa.Signal 9 | let badgeDidDeselect: RxCocoa.Signal 10 | 11 | private let selectedModel: SelectedBadgesModel 12 | private let disposeBag = RxSwift.DisposeBag() 13 | 14 | 15 | init( 16 | input selectedTap: RxCocoa.Signal, 17 | dependency selectedModel: SelectedBadgesModel 18 | ) { 19 | self.selectedModel = selectedModel 20 | self.selectedBadges = selectedModel.selectionDidChange 21 | self.badgeDidSelect = selectedModel.badgeDidSelect 22 | self.badgeDidDeselect = selectedModel.badgeDidDeselect 23 | 24 | selectedTap 25 | .emit(onNext: { [weak self] badge in 26 | guard let `self` = self else { return } 27 | 28 | self.selectedModel.deselect(badge: badge) 29 | }) 30 | .disposed(by: self.disposeBag) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Views/BadgeSelectorRootView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | class BadgeSelectorRootView: UIView { 5 | @IBOutlet weak var selectedCollectionOutlet: UICollectionView! { 6 | didSet { 7 | let layout = UICollectionViewFlowLayout() 8 | layout.scrollDirection = .horizontal 9 | layout.itemSize = CGSize(width: 80, height: 80) 10 | layout.minimumLineSpacing = 10 11 | layout.minimumInteritemSpacing = 10 12 | layout.sectionInset = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) 13 | self.selectedCollectionOutlet.collectionViewLayout = layout 14 | SelectedBadgeCell.register(to: self.selectedCollectionOutlet) 15 | } 16 | } 17 | 18 | 19 | @IBOutlet weak var selectableCollectionOutlet: UICollectionView! { 20 | didSet { 21 | let layout = UICollectionViewFlowLayout() 22 | layout.scrollDirection = .vertical 23 | layout.itemSize = CGSize(width: 80, height: 80) 24 | layout.minimumLineSpacing = 10 25 | layout.minimumInteritemSpacing = 10 26 | layout.sectionInset = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) 27 | self.selectableCollectionOutlet.collectionViewLayout = layout 28 | self.selectableCollectionOutlet.contentInset = UIEdgeInsets(top: 100, left: 0, bottom: 0, right: 0) 29 | SelectableBadgeCell.register(to: self.selectableCollectionOutlet) 30 | } 31 | } 32 | 33 | 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | self.loadFromXib() 37 | } 38 | 39 | 40 | required init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | self.loadFromXib() 43 | } 44 | 45 | 46 | private func loadFromXib() { 47 | guard let view = BadgeSelectorRootView.loadViewFromXib(owner: self) else { return } 48 | 49 | view.translatesAutoresizingMaskIntoConstraints = false 50 | self.addSubview(view) 51 | 52 | FilledLayout.fill(subview: view, into: self) 53 | } 54 | 55 | 56 | private static let nibName = "BadgeSelectorScreen" 57 | 58 | 59 | private static func loadViewFromXib(owner: BadgeSelectorRootView) -> UIView? { 60 | let nib = UINib(nibName: self.nibName, bundle: Bundle(for: self)) 61 | return nib.instantiate(withOwner: owner).first as? UIView 62 | } 63 | } 64 | 65 | 66 | 67 | @IBDesignable class BadgesSelectorRootViewSelectedAreaView: UIVisualEffectView { 68 | required init?(coder aDecoder: NSCoder) { 69 | super.init(coder: aDecoder) 70 | self.layer.shadowOpacity = 0.15 71 | self.layer.shadowRadius = 0 72 | self.layer.shadowOffset = CGSize(width: 0, height: 1) 73 | self.layer.shadowColor = UIColor.black.cgColor 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Views/SelectableBadgeCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | class SelectableBadgeCell: UICollectionViewCell { 6 | @IBOutlet weak var titleOutlet: UILabel! 7 | @IBOutlet weak var backgroundOutlet: SelectableBadgeCellRoundedBackground! 8 | 9 | 10 | var badge: Badge? { 11 | didSet { 12 | guard let badge = self.badge else { 13 | self.titleOutlet?.text = nil 14 | self.backgroundOutlet?.backgroundColor = UIColor.black 15 | return 16 | } 17 | 18 | self.titleOutlet?.text = badge.title 19 | self.backgroundOutlet?.backgroundColor = badge.color 20 | } 21 | } 22 | 23 | 24 | private static let reuseIdentifier = "SelectableBadgeCell" 25 | private static let nibName = "SelectableBadgeCell" 26 | 27 | 28 | static func register(to collectionView: UICollectionView) { 29 | let nib = UINib(nibName: self.nibName, bundle: Bundle(for: self)) 30 | collectionView.register(nib, forCellWithReuseIdentifier: self.reuseIdentifier) 31 | } 32 | 33 | 34 | static func dequeue(from collectionView: UICollectionView, for indexPath: IndexPath, badge: Badge) -> UICollectionViewCell { 35 | guard let cell = collectionView.dequeueReusableCell( 36 | withReuseIdentifier: self.reuseIdentifier, 37 | for: indexPath 38 | ) as? SelectableBadgeCell else { 39 | fatalError("Please register \(self.reuseIdentifier)") 40 | } 41 | 42 | cell.badge = badge 43 | 44 | return cell 45 | } 46 | } 47 | 48 | 49 | 50 | @IBDesignable 51 | class SelectableBadgeCellRoundedBackground: UIView { 52 | override func layoutSubviews() { 53 | super.layoutSubviews() 54 | self.layer.cornerRadius = min(self.bounds.width, self.bounds.height) / 2 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Views/SelectedBadgeCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | class SelectedBadgeCell: UICollectionViewCell { 6 | @IBOutlet weak var titleOutlet: UILabel! 7 | @IBOutlet weak var backgroundOutlet: SelectedBadgeCellRoundedBackground! 8 | 9 | 10 | var badge: Badge? { 11 | didSet { 12 | guard let badge = self.badge else { 13 | self.titleOutlet?.text = nil 14 | self.backgroundOutlet?.backgroundColor = UIColor.black 15 | return 16 | } 17 | 18 | self.titleOutlet?.text = badge.title 19 | self.backgroundOutlet?.backgroundColor = badge.color 20 | } 21 | } 22 | 23 | 24 | static let reuseIdentifier = "SelectedBadgeCell" 25 | static let nibName = "SelectedBadgeCell" 26 | 27 | 28 | static func register(to collectionView: UICollectionView) { 29 | let nib = UINib(nibName: self.nibName, bundle: Bundle(for: self)) 30 | collectionView.register(nib, forCellWithReuseIdentifier: self.reuseIdentifier) 31 | } 32 | 33 | 34 | static func dequeue(from collectionView: UICollectionView, for indexPath: IndexPath, badge: Badge) -> UICollectionViewCell { 35 | guard let cell = collectionView.dequeueReusableCell( 36 | withReuseIdentifier: self.reuseIdentifier, 37 | for: indexPath 38 | ) as? SelectedBadgeCell else { 39 | fatalError("Please register \(self.reuseIdentifier)") 40 | } 41 | 42 | cell.badge = badge 43 | 44 | return cell 45 | } 46 | } 47 | 48 | 49 | 50 | @IBDesignable class SelectedBadgeCellRoundedBackground: UIView { 51 | override func layoutSubviews() { 52 | super.layoutSubviews() 53 | self.layer.cornerRadius = min(self.bounds.width, self.bounds.height) / 2 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Wireframes/BadgeSelectorWireframe.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | protocol BadgeSelectorWireframe { 6 | func goToResultScreen(with selectedBadgesModel: SelectedBadgesModel) 7 | } 8 | 9 | 10 | 11 | class DefaultBadgeSelectorWireframe: BadgeSelectorWireframe { 12 | private weak var viewController: UIViewController? 13 | 14 | 15 | init(on viewController: UIViewController) { 16 | self.viewController = viewController 17 | } 18 | 19 | 20 | func goToResultScreen(with selectedBadgesModel: SelectedBadgesModel) { 21 | let navigationController = UINavigationController( 22 | rootViewController: SelectedBadgesViewController( 23 | dependency: selectedBadgesModel 24 | ) 25 | ) 26 | 27 | self.viewController?.present(navigationController, animated: true) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Xibs/BadgeSelectorScreen.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 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Xibs/SelectableBadgeCell.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 | 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 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/SelectingBadgesScreen/Xibs/SelectedBadgeCell.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 | 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 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/Shared/Entities/Badge.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxDataSources 3 | 4 | 5 | 6 | struct Badge { 7 | let id: Int 8 | let title: String 9 | let color: UIColor 10 | } 11 | 12 | 13 | 14 | extension Badge: Hashable { 15 | var hashValue: Int { 16 | return self.id 17 | } 18 | 19 | 20 | static func ==(lhs: Badge, rhs: Badge) -> Bool { 21 | return lhs.id == rhs.id 22 | } 23 | } 24 | 25 | 26 | 27 | extension Badge: RxDataSources.IdentifiableType { 28 | typealias Identity = Int 29 | 30 | var identity: Identity { 31 | return self.id 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SharedModelExample/Shared/Models/SelectedBadgesModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | protocol SelectedBadgesModel { 7 | var selectionDidChange: RxCocoa.Driver<[Badge]> { get } 8 | var currentSelection: [Badge] { get } 9 | var badgeDidSelect: RxCocoa.Signal { get } 10 | var badgeDidDeselect: RxCocoa.Signal { get } 11 | 12 | func select(badge: Badge) 13 | func deselect(badge: Badge) 14 | } 15 | 16 | 17 | 18 | class DefaultSelectedBadgesModel: SelectedBadgesModel { 19 | private let stateMachine: StateMachine<[Badge]> 20 | private let badgeDidSelectRelay: RxCocoa.PublishRelay 21 | private let badgeDidDeselectRelay: RxCocoa.PublishRelay 22 | 23 | let badgeDidSelect: RxCocoa.Signal 24 | let badgeDidDeselect: RxCocoa.Signal 25 | let selectionDidChange: RxCocoa.Driver<[Badge]> 26 | 27 | 28 | var currentSelection: [Badge] { 29 | get { return self.stateMachine.currentState } 30 | set { self.stateMachine.currentState = newValue } 31 | } 32 | 33 | 34 | init(selected initialSelection: [Badge]) { 35 | self.stateMachine = StateMachine( 36 | startingWith: initialSelection 37 | ) 38 | 39 | let badgeDidSelectRelay = RxCocoa.PublishRelay() 40 | self.badgeDidSelectRelay = badgeDidSelectRelay 41 | self.badgeDidSelect = badgeDidSelectRelay.asSignal() 42 | 43 | let badgeDidDeselectRelay = RxCocoa.PublishRelay() 44 | self.badgeDidDeselectRelay = badgeDidDeselectRelay 45 | self.badgeDidDeselect = badgeDidDeselectRelay.asSignal() 46 | 47 | self.selectionDidChange = stateMachine.stateDidChange 48 | } 49 | 50 | 51 | func select(badge: Badge) { 52 | guard !self.currentSelection.contains(badge) else { return } 53 | 54 | var newSelection = self.currentSelection 55 | newSelection.append(badge) 56 | 57 | self.currentSelection = newSelection 58 | 59 | self.badgeDidSelectRelay.accept(badge) 60 | } 61 | 62 | 63 | func deselect(badge: Badge) { 64 | var newSelection = self.currentSelection 65 | 66 | guard let index = newSelection.index(of: badge) else { return } 67 | 68 | newSelection.remove(at: index) 69 | 70 | self.currentSelection = newSelection 71 | 72 | self.badgeDidDeselectRelay.accept(badge) 73 | } 74 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/SimpleExample/ViewControllers/SimpleViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import RxSwift 3 | import RxCocoa 4 | 5 | 6 | 7 | class SimpleViewController: UIViewController { 8 | private let rootView: SimpleRootView 9 | private var viewModel: SimpleViewModel? 10 | private let disposeBag = RxSwift.DisposeBag() 11 | 12 | 13 | init() { 14 | self.rootView = SimpleRootView() 15 | super.init(nibName: nil, bundle: nil) 16 | } 17 | 18 | 19 | required init?(coder aDecoder: NSCoder) { 20 | return nil 21 | } 22 | 23 | 24 | override func loadView() { 25 | self.view = self.rootView 26 | 27 | self.title = "Echo" 28 | } 29 | 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | let viewModel = SimpleViewModel(input: rootView.inputOutlet.rx.text.asDriver()) 35 | 36 | viewModel.outputText 37 | .drive(rootView.outputOutlet.rx.text) 38 | .disposed(by: self.disposeBag) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SimpleExample/ViewModels/SimpleViewModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class SimpleViewModel { 7 | let outputText: RxCocoa.Driver 8 | 9 | 10 | init(input inputText: RxCocoa.Driver) { 11 | self.outputText = inputText 12 | .map { text in text?.uppercased() } 13 | .asDriver() 14 | } 15 | } -------------------------------------------------------------------------------- /RxNextExample/Examples/SimpleExample/Views/SimpleRootView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | class SimpleRootView: UIView { 6 | @IBOutlet weak var outputOutlet: UILabel! 7 | @IBOutlet weak var inputOutlet: UITextField! 8 | 9 | 10 | override init(frame: CGRect) { 11 | super.init(frame: frame) 12 | self.setUp() 13 | } 14 | 15 | 16 | required init?(coder aDecoder: NSCoder) { 17 | super.init(coder: aDecoder) 18 | self.setUp() 19 | } 20 | 21 | 22 | private func setUp() { 23 | guard let view = SimpleRootView.loadViewFromXib(owner: self) else { return } 24 | 25 | view.translatesAutoresizingMaskIntoConstraints = false 26 | self.addSubview(view) 27 | 28 | FilledLayout.fill(subview: view, into: self) 29 | } 30 | 31 | 32 | private static let nibName = "SimpleScreen" 33 | 34 | 35 | private static func loadViewFromXib(owner: SimpleRootView) -> UIView? { 36 | let nib = UINib(nibName: self.nibName, bundle: Bundle(for: self)) 37 | return nib.instantiate(withOwner: owner).first as? UIView 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /RxNextExample/Examples/SimpleExample/Xibs/SimpleScreen.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 | 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 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /RxNextExample/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /RxNextExample/Shared/Models/EntityModel.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class EntityModel { 7 | typealias P = Parameters 8 | typealias V = Value 9 | typealias E = Reason 10 | 11 | let stateDidChange: RxCocoa.Driver> 12 | 13 | private let stateMachine: StateMachine> 14 | private let repository: AnyEntityModelRepository 15 | private let disposeBag = RxSwift.DisposeBag() 16 | 17 | 18 | var currentState: EntityModelState { 19 | get { return self.stateMachine.currentState } 20 | set { self.stateMachine.currentState = newValue } 21 | } 22 | 23 | 24 | init( 25 | startingWith initialState: EntityModelState, 26 | gettingEntityBy repository: Repository 27 | ) where Repository.P == Parameters, Repository.V == Value, Repository.E == Reason { 28 | self.stateMachine = StateMachine(startingWith: initialState) 29 | self.stateDidChange = stateMachine.stateDidChange 30 | self.repository = repository.asAny() 31 | } 32 | 33 | 34 | func get(by parameters: P) { 35 | self.repository 36 | .get(by: parameters) 37 | .subscribe( 38 | onSuccess: { [weak self] response in 39 | guard let `self` = self else { return } 40 | 41 | switch response { 42 | case let .success(value): 43 | self.currentState = .fulfilled(by: value) 44 | case let .failure(error): 45 | self.currentState = .rejected(because: .causedByRepository(error: error)) 46 | } 47 | }, 48 | onError: { [weak self] error in 49 | guard let `self` = self else { return } 50 | 51 | self.currentState = .rejected(because: .causedBySingle(error: error)) 52 | } 53 | ) 54 | .disposed(by: self.disposeBag) 55 | } 56 | } 57 | 58 | 59 | 60 | indirect enum EntityModelState { 61 | case sleeping 62 | case triggered(by: P) 63 | case fulfilled(by: V) 64 | case rejected(because: Reason) 65 | 66 | 67 | var isPending: Bool { 68 | switch self { 69 | case .sleeping, .triggered: 70 | return true 71 | case .fulfilled, .rejected: 72 | return false 73 | } 74 | } 75 | 76 | 77 | var isCompleted: Bool { 78 | return !self.isPending 79 | } 80 | 81 | 82 | var parameters: P? { 83 | switch self { 84 | case let .triggered(by: parameters): 85 | return parameters 86 | case .sleeping, .fulfilled, .rejected: 87 | return nil 88 | } 89 | } 90 | 91 | 92 | var value: V? { 93 | switch self { 94 | case let .fulfilled(by: value): 95 | return value 96 | case .sleeping, .triggered, .rejected: 97 | return nil 98 | } 99 | } 100 | 101 | 102 | var reason: Reason? { 103 | switch self { 104 | case let .rejected(because: reason): 105 | return reason 106 | case .sleeping, .triggered, .fulfilled: 107 | return nil 108 | } 109 | } 110 | 111 | 112 | enum Reason { 113 | case causedBySingle(error: Error) 114 | case causedByRepository(error: E) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /RxNextExample/Shared/Models/StateMachine.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import RxCocoa 3 | 4 | 5 | 6 | class StateMachine { 7 | fileprivate let stateRelay: RxCocoa.BehaviorRelay 8 | let stateDidChange: Driver 9 | 10 | 11 | var currentState: S { 12 | get { return self.stateRelay.value } 13 | set { self.stateRelay.accept(newValue) } 14 | } 15 | 16 | 17 | init(startingWith initialState: S) { 18 | let stateRelay = RxCocoa.BehaviorRelay(value: initialState) 19 | self.stateRelay = stateRelay 20 | self.stateDidChange = stateRelay.asDriver() 21 | } 22 | } 23 | 24 | 25 | 26 | extension SharedSequenceConvertibleType where Self.SharingStrategy == RxCocoa.DriverSharingStrategy { 27 | func drive(stateMachine: StateMachine) -> RxSwift.Disposable { 28 | return self.drive(stateMachine.stateRelay) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RxNextExample/Shared/Repositories/EntityModelRepository.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | 3 | 4 | 5 | protocol EntityModelRepository { 6 | associatedtype V 7 | associatedtype E 8 | associatedtype P 9 | 10 | func get(by parameters: P) -> RxSwift.Single> 11 | } 12 | 13 | 14 | 15 | extension EntityModelRepository { 16 | func asAny() -> AnyEntityModelRepository { 17 | return AnyEntityModelRepository(self) 18 | } 19 | } 20 | 21 | 22 | 23 | class AnyEntityModelRepository: EntityModelRepository { 24 | typealias V = Entity 25 | typealias E = Reason 26 | typealias P = Parameters 27 | 28 | 29 | private let _get: (Parameters) -> RxSwift.Single> 30 | 31 | 32 | init( 33 | _ repository: Repository 34 | ) where Repository.V == Entity, Repository.E == Reason, Repository.P == Parameters { 35 | self._get = { parameters in 36 | repository.get(by: parameters) 37 | } 38 | } 39 | 40 | 41 | func get(by parameters: P) -> RxSwift.Single> { 42 | return self._get(parameters) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /RxNextExample/Shared/Result.swift: -------------------------------------------------------------------------------- 1 | enum Result { 2 | case success(V) 3 | case failure(E) 4 | 5 | 6 | var value: V? { 7 | switch self { 8 | case let .success(value): 9 | return value 10 | case .failure: 11 | return nil 12 | } 13 | } 14 | 15 | 16 | var error: E? { 17 | switch self { 18 | case let .failure(error): 19 | return error 20 | case .success: 21 | return nil 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RxNextExample/Shared/Views/BackButtonTitleHandle.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | enum BackButtonTitleHandler { 6 | static func hideBackButtonTitle(of viewController: UIViewController) { 7 | viewController.navigationItem.backBarButtonItem = UIBarButtonItem( 8 | title: "", 9 | style: .plain, 10 | target: nil, 11 | action: nil 12 | ) 13 | } 14 | } -------------------------------------------------------------------------------- /RxNextExample/Shared/Views/Layers/FilledLayout.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | enum FilledLayout { 6 | static func fill(subview: UIView, into superview: UIView) { 7 | let constraints: [NSLayoutConstraint] = [ 8 | superview.topAnchor.constraint(equalTo: subview.topAnchor), 9 | superview.bottomAnchor.constraint(equalTo: subview.bottomAnchor), 10 | superview.leftAnchor.constraint(equalTo: subview.leftAnchor), 11 | superview.rightAnchor.constraint(equalTo: subview.rightAnchor), 12 | ] 13 | 14 | constraints.forEach { constraint in 15 | constraint.isActive = true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RxNextExample/Shared/Wireframes/PresentedViewControllerWireframe.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | 5 | protocol PresentedViewControllerWireframe { 6 | func goBack() 7 | } 8 | 9 | 10 | 11 | class DefaultPresentedViewControllerWireframe: PresentedViewControllerWireframe { 12 | private weak var presentedViewController: UIViewController? 13 | 14 | 15 | init(willDismiss presentedViewController: UIViewController) { 16 | self.presentedViewController = presentedViewController 17 | } 18 | 19 | 20 | func goBack() { 21 | self.presentedViewController?.dismiss(animated: true) 22 | } 23 | } -------------------------------------------------------------------------------- /RxNextExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // RxNextExample 4 | // 5 | // Created by yuki.kokubun on 2017/12/08. 6 | // Copyright © 2017年 kuniwak. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /RxNextExampleTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | --------------------------------------------------------------------------------