├── .gitignore ├── .swift-version ├── .travis.yml ├── Example ├── Podfile ├── Podfile.lock ├── RxMVC.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── RxMVC-Example.xcscheme ├── RxMVC.xcworkspace │ └── contents.xcworkspacedata ├── RxMVC │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Main.storyboard │ ├── Utils │ │ └── GitHubAPI.swift │ └── ViewControllers │ │ ├── GitHubSearchController.swift │ │ ├── GitHubSearchModel.swift │ │ ├── GitHubSearchTypes.swift │ │ ├── GitHubSearchView.swift │ │ ├── GitHubSearchViewController.swift │ │ ├── MasterViewController.swift │ │ └── UpDownViewController.swift └── Tests │ ├── Example │ └── UpDownTest.swift │ ├── Info.plist │ └── ModelTest.swift ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Resources └── MVC_Cocoa.png ├── RxMVC.podspec ├── RxMVC ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── Controller.swift │ ├── MVC.swift │ ├── Model.swift │ ├── UserInteractable.swift │ └── View.swift └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | Pods/ 34 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode7.3 2 | rvm: 3 | - 2.3.1 4 | language: objective-c 5 | cache: 6 | - bundler 7 | - cocoapods 8 | podfile: Example/Podfile 9 | before_install: 10 | - bundle install 11 | - pod repo remove master 12 | - pod setup 13 | install: 14 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 15 | - bundle exec pod install --project-directory=Example 16 | script: 17 | - > 18 | set -o pipefail && 19 | xcodebuild test 20 | -workspace Example/RxMVC.xcworkspace 21 | -scheme RxMVC-Example 22 | -sdk iphonesimulator9.3 23 | -jobs `sysctl hw.ncpu | awk '{print 2*$2}'` 24 | ONLY_ACTIVE_ARCH=NO | xcpretty 25 | - pod lib lint 26 | 27 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'RxMVC_Example' do 4 | platform :ios, '9.0' 5 | pod 'RxMVC', :path => '../' 6 | pod 'Reachability', '~> 3.0' 7 | pod 'RxAlamofire', '~> 3.0.0-beta.1' 8 | pod 'Alamofire', '~> 4.0' 9 | pod 'Toaster', '~> 2.0' 10 | 11 | target 'RxMVC_Tests' do 12 | inherit! :search_paths 13 | platform :ios, '9.0' 14 | 15 | pod 'RxBlocking', '~> 3.0.0-beta.1' 16 | pod 'RxTests', '~> 3.0.0-beta.1' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.0.1) 3 | - Reachability (3.2) 4 | - RxAlamofire (3.0.0-beta.1): 5 | - RxAlamofire/Core (= 3.0.0-beta.1) 6 | - RxAlamofire/Core (3.0.0-beta.1): 7 | - Alamofire (~> 4.0) 8 | - RxCocoa (~> 3.0.0-beta.1) 9 | - RxSwift (~> 3.0.0-beta.1) 10 | - RxBlocking (3.0.0-beta.1): 11 | - RxSwift (~> 3.0.0-beta.1) 12 | - RxCocoa (3.0.0-beta.1): 13 | - RxSwift (~> 3.0.0-beta.1) 14 | - RxMVC (0.2.0-beta.2): 15 | - RxCocoa (~> 3.0.0-beta.1) 16 | - RxSwift (~> 3.0.0-beta.1) 17 | - RxSwift (3.0.0-beta.1) 18 | - RxTests (3.0.0-beta.1): 19 | - RxSwift (~> 3.0.0-beta.1) 20 | - Toaster (2.0.0) 21 | 22 | DEPENDENCIES: 23 | - Alamofire (~> 4.0) 24 | - Reachability (~> 3.0) 25 | - RxAlamofire (~> 3.0.0-beta.1) 26 | - RxBlocking (~> 3.0.0-beta.1) 27 | - RxMVC (from `../`) 28 | - RxTests (~> 3.0.0-beta.1) 29 | - Toaster (~> 2.0) 30 | 31 | EXTERNAL SOURCES: 32 | RxMVC: 33 | :path: "../" 34 | 35 | SPEC CHECKSUMS: 36 | Alamofire: 7682d43245de14874acd142ec137b144aa1dd335 37 | Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 38 | RxAlamofire: 3e62073fbe1c0269c942af44b073c10d9dda81ab 39 | RxBlocking: 422944880fe63b432c459dc87be7c8178dcf45f3 40 | RxCocoa: 8cecf331302b8ae5381bbab1bccba4ede2eae6a8 41 | RxMVC: 6ba2aa4457723634719fb241c8d784947f1ac941 42 | RxSwift: 0823e8d7969c23bfa9ddfb2afa4881e424a1a710 43 | RxTests: 819cbca750007349f03cfdb89bc07d412f67ab96 44 | Toaster: 25101e968655aa991fa72ad888029e8d515c6237 45 | 46 | PODFILE CHECKSUM: 612d3313b445ef12de3055680de4a11ebecc230f 47 | 48 | COCOAPODS: 1.1.0.rc.2 49 | -------------------------------------------------------------------------------- /Example/RxMVC.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 235C93951CCE2CB700E67605 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 235C93901CCE2CB700E67605 /* AppDelegate.swift */; }; 11 | 235C93991CCE2CB700E67605 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 235C93941CCE2CB700E67605 /* Main.storyboard */; }; 12 | 5A7A266F1D30CB2900219E63 /* UpDownTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A7A266D1D30CB2400219E63 /* UpDownTest.swift */; }; 13 | 5A7A26711D30CB4A00219E63 /* ModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A7A26701D30CB4A00219E63 /* ModelTest.swift */; }; 14 | 5AB80BB21D28402D00C13E1E /* GitHubSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB80BAF1D28402D00C13E1E /* GitHubSearchViewController.swift */; }; 15 | 5AB80BB31D28402D00C13E1E /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB80BB01D28402D00C13E1E /* MasterViewController.swift */; }; 16 | 5AB80BB41D28402D00C13E1E /* UpDownViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB80BB11D28402D00C13E1E /* UpDownViewController.swift */; }; 17 | 5AB80BB71D28403500C13E1E /* GitHubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB80BB61D28403500C13E1E /* GitHubAPI.swift */; }; 18 | 5AB80BBA1D28408000C13E1E /* GitHubSearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB80BB91D28408000C13E1E /* GitHubSearchModel.swift */; }; 19 | 5AB80BBD1D28408F00C13E1E /* GitHubSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB80BBC1D28408F00C13E1E /* GitHubSearchController.swift */; }; 20 | 5AB80BBF1D2840EB00C13E1E /* GitHubSearchTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB80BBE1D2840EB00C13E1E /* GitHubSearchTypes.swift */; }; 21 | 5AF1FEF41D2D3F4F00018671 /* GitHubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF1FEF31D2D3F4F00018671 /* GitHubSearchView.swift */; }; 22 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 23 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 24 | 698EE95498280C4D72969284 /* Pods_RxMVC_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29FB0BBE892E25D5DB0879E8 /* Pods_RxMVC_Example.framework */; }; 25 | B9AA200CB24503358EFAA644 /* Pods_RxMVC_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B13A29920C873DAC049F638A /* Pods_RxMVC_Tests.framework */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 34 | remoteInfo = RxMVC; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 235C93901CCE2CB700E67605 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 235C93941CCE2CB700E67605 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 41 | 29FB0BBE892E25D5DB0879E8 /* Pods_RxMVC_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxMVC_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 5A7A266D1D30CB2400219E63 /* UpDownTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpDownTest.swift; sourceTree = ""; }; 43 | 5A7A26701D30CB4A00219E63 /* ModelTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelTest.swift; sourceTree = ""; }; 44 | 5AB80BAF1D28402D00C13E1E /* GitHubSearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubSearchViewController.swift; sourceTree = ""; }; 45 | 5AB80BB01D28402D00C13E1E /* MasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 46 | 5AB80BB11D28402D00C13E1E /* UpDownViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpDownViewController.swift; sourceTree = ""; }; 47 | 5AB80BB61D28403500C13E1E /* GitHubAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubAPI.swift; sourceTree = ""; }; 48 | 5AB80BB91D28408000C13E1E /* GitHubSearchModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubSearchModel.swift; sourceTree = ""; }; 49 | 5AB80BBC1D28408F00C13E1E /* GitHubSearchController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubSearchController.swift; sourceTree = ""; }; 50 | 5AB80BBE1D2840EB00C13E1E /* GitHubSearchTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubSearchTypes.swift; sourceTree = ""; }; 51 | 5AF1FEF31D2D3F4F00018671 /* GitHubSearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubSearchView.swift; sourceTree = ""; }; 52 | 607FACD01AFB9204008FA782 /* RxMVC_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxMVC_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 55 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 56 | 607FACE51AFB9204008FA782 /* RxMVC_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxMVC_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 64F2E66B3CFB98E9E4EF9350 /* Pods-RxMVC_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxMVC_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxMVC_Example/Pods-RxMVC_Example.debug.xcconfig"; sourceTree = ""; }; 59 | 762337A399CB79B60E5F0239 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 60 | AA1D28E0F1EF023DA619F8C0 /* RxMVC.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = RxMVC.podspec; path = ../RxMVC.podspec; sourceTree = ""; }; 61 | B13A29920C873DAC049F638A /* Pods_RxMVC_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxMVC_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | BC0A58DAA3BC59E6B0BEB187 /* Pods-RxMVC_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxMVC_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxMVC_Example/Pods-RxMVC_Example.release.xcconfig"; sourceTree = ""; }; 63 | E2721FBB958801016129AB50 /* Pods-RxMVC_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxMVC_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxMVC_Tests/Pods-RxMVC_Tests.debug.xcconfig"; sourceTree = ""; }; 64 | F91DEF3F008693429ABB0F2C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 65 | FC481AAF4732B6F4A2A15EEC /* Pods-RxMVC_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxMVC_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxMVC_Tests/Pods-RxMVC_Tests.release.xcconfig"; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 698EE95498280C4D72969284 /* Pods_RxMVC_Example.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | B9AA200CB24503358EFAA644 /* Pods_RxMVC_Tests.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 5A7A266C1D30CB2400219E63 /* Example */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 5A7A266D1D30CB2400219E63 /* UpDownTest.swift */, 92 | ); 93 | path = Example; 94 | sourceTree = ""; 95 | }; 96 | 5AB80BAE1D28402D00C13E1E /* ViewControllers */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 5AB80BB81D28404F00C13E1E /* GitHubSearch */, 100 | 5AB80BB01D28402D00C13E1E /* MasterViewController.swift */, 101 | 5AB80BB11D28402D00C13E1E /* UpDownViewController.swift */, 102 | ); 103 | path = ViewControllers; 104 | sourceTree = ""; 105 | }; 106 | 5AB80BB51D28403500C13E1E /* Utils */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 5AB80BB61D28403500C13E1E /* GitHubAPI.swift */, 110 | ); 111 | path = Utils; 112 | sourceTree = ""; 113 | }; 114 | 5AB80BB81D28404F00C13E1E /* GitHubSearch */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 5AB80BAF1D28402D00C13E1E /* GitHubSearchViewController.swift */, 118 | 5AB80BB91D28408000C13E1E /* GitHubSearchModel.swift */, 119 | 5AB80BBC1D28408F00C13E1E /* GitHubSearchController.swift */, 120 | 5AB80BBE1D2840EB00C13E1E /* GitHubSearchTypes.swift */, 121 | 5AF1FEF31D2D3F4F00018671 /* GitHubSearchView.swift */, 122 | ); 123 | name = GitHubSearch; 124 | sourceTree = ""; 125 | }; 126 | 5BD3FB7046AE6807D44CB91F /* Frameworks */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 29FB0BBE892E25D5DB0879E8 /* Pods_RxMVC_Example.framework */, 130 | B13A29920C873DAC049F638A /* Pods_RxMVC_Tests.framework */, 131 | ); 132 | name = Frameworks; 133 | sourceTree = ""; 134 | }; 135 | 607FACC71AFB9204008FA782 = { 136 | isa = PBXGroup; 137 | children = ( 138 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 139 | 607FACD21AFB9204008FA782 /* Example for RxMVC */, 140 | 607FACE81AFB9204008FA782 /* Tests */, 141 | 607FACD11AFB9204008FA782 /* Products */, 142 | D68A514BD155B3A906AF51B8 /* Pods */, 143 | 5BD3FB7046AE6807D44CB91F /* Frameworks */, 144 | ); 145 | sourceTree = ""; 146 | }; 147 | 607FACD11AFB9204008FA782 /* Products */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 607FACD01AFB9204008FA782 /* RxMVC_Example.app */, 151 | 607FACE51AFB9204008FA782 /* RxMVC_Tests.xctest */, 152 | ); 153 | name = Products; 154 | sourceTree = ""; 155 | }; 156 | 607FACD21AFB9204008FA782 /* Example for RxMVC */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 235C93901CCE2CB700E67605 /* AppDelegate.swift */, 160 | 235C93941CCE2CB700E67605 /* Main.storyboard */, 161 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 162 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 163 | 5AB80BAE1D28402D00C13E1E /* ViewControllers */, 164 | 5AB80BB51D28403500C13E1E /* Utils */, 165 | 607FACD31AFB9204008FA782 /* Supporting Files */, 166 | ); 167 | name = "Example for RxMVC"; 168 | path = RxMVC; 169 | sourceTree = ""; 170 | }; 171 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 607FACD41AFB9204008FA782 /* Info.plist */, 175 | ); 176 | name = "Supporting Files"; 177 | sourceTree = ""; 178 | }; 179 | 607FACE81AFB9204008FA782 /* Tests */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 5A7A266C1D30CB2400219E63 /* Example */, 183 | 607FACE91AFB9204008FA782 /* Supporting Files */, 184 | 5A7A26701D30CB4A00219E63 /* ModelTest.swift */, 185 | ); 186 | path = Tests; 187 | sourceTree = ""; 188 | }; 189 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 607FACEA1AFB9204008FA782 /* Info.plist */, 193 | ); 194 | name = "Supporting Files"; 195 | sourceTree = ""; 196 | }; 197 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | AA1D28E0F1EF023DA619F8C0 /* RxMVC.podspec */, 201 | F91DEF3F008693429ABB0F2C /* README.md */, 202 | 762337A399CB79B60E5F0239 /* LICENSE */, 203 | ); 204 | name = "Podspec Metadata"; 205 | sourceTree = ""; 206 | }; 207 | D68A514BD155B3A906AF51B8 /* Pods */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 64F2E66B3CFB98E9E4EF9350 /* Pods-RxMVC_Example.debug.xcconfig */, 211 | BC0A58DAA3BC59E6B0BEB187 /* Pods-RxMVC_Example.release.xcconfig */, 212 | E2721FBB958801016129AB50 /* Pods-RxMVC_Tests.debug.xcconfig */, 213 | FC481AAF4732B6F4A2A15EEC /* Pods-RxMVC_Tests.release.xcconfig */, 214 | ); 215 | name = Pods; 216 | sourceTree = ""; 217 | }; 218 | /* End PBXGroup section */ 219 | 220 | /* Begin PBXNativeTarget section */ 221 | 607FACCF1AFB9204008FA782 /* RxMVC_Example */ = { 222 | isa = PBXNativeTarget; 223 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxMVC_Example" */; 224 | buildPhases = ( 225 | A341C09CE3DD2E385D069310 /* [CP] Check Pods Manifest.lock */, 226 | 607FACCC1AFB9204008FA782 /* Sources */, 227 | 607FACCD1AFB9204008FA782 /* Frameworks */, 228 | 607FACCE1AFB9204008FA782 /* Resources */, 229 | 89842A207F22DAE587BD45FC /* [CP] Embed Pods Frameworks */, 230 | 1796B3AB299F80B75A6FCFD4 /* [CP] Copy Pods Resources */, 231 | ); 232 | buildRules = ( 233 | ); 234 | dependencies = ( 235 | ); 236 | name = RxMVC_Example; 237 | productName = RxMVC; 238 | productReference = 607FACD01AFB9204008FA782 /* RxMVC_Example.app */; 239 | productType = "com.apple.product-type.application"; 240 | }; 241 | 607FACE41AFB9204008FA782 /* RxMVC_Tests */ = { 242 | isa = PBXNativeTarget; 243 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxMVC_Tests" */; 244 | buildPhases = ( 245 | A5A4C35C630FEB284860B5C4 /* [CP] Check Pods Manifest.lock */, 246 | 607FACE11AFB9204008FA782 /* Sources */, 247 | 607FACE21AFB9204008FA782 /* Frameworks */, 248 | 607FACE31AFB9204008FA782 /* Resources */, 249 | BF60312CC157BC0A3383AFDE /* [CP] Embed Pods Frameworks */, 250 | F5828CE1B97F891CD5F0E59F /* [CP] Copy Pods Resources */, 251 | ); 252 | buildRules = ( 253 | ); 254 | dependencies = ( 255 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 256 | ); 257 | name = RxMVC_Tests; 258 | productName = Tests; 259 | productReference = 607FACE51AFB9204008FA782 /* RxMVC_Tests.xctest */; 260 | productType = "com.apple.product-type.bundle.unit-test"; 261 | }; 262 | /* End PBXNativeTarget section */ 263 | 264 | /* Begin PBXProject section */ 265 | 607FACC81AFB9204008FA782 /* Project object */ = { 266 | isa = PBXProject; 267 | attributes = { 268 | LastSwiftUpdateCheck = 0720; 269 | LastUpgradeCheck = 0800; 270 | ORGANIZATIONNAME = CocoaPods; 271 | TargetAttributes = { 272 | 607FACCF1AFB9204008FA782 = { 273 | CreatedOnToolsVersion = 6.3.1; 274 | LastSwiftMigration = 0800; 275 | }; 276 | 607FACE41AFB9204008FA782 = { 277 | CreatedOnToolsVersion = 6.3.1; 278 | LastSwiftMigration = 0800; 279 | TestTargetID = 607FACCF1AFB9204008FA782; 280 | }; 281 | }; 282 | }; 283 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "RxMVC" */; 284 | compatibilityVersion = "Xcode 3.2"; 285 | developmentRegion = English; 286 | hasScannedForEncodings = 0; 287 | knownRegions = ( 288 | en, 289 | Base, 290 | ); 291 | mainGroup = 607FACC71AFB9204008FA782; 292 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 293 | projectDirPath = ""; 294 | projectRoot = ""; 295 | targets = ( 296 | 607FACCF1AFB9204008FA782 /* RxMVC_Example */, 297 | 607FACE41AFB9204008FA782 /* RxMVC_Tests */, 298 | ); 299 | }; 300 | /* End PBXProject section */ 301 | 302 | /* Begin PBXResourcesBuildPhase section */ 303 | 607FACCE1AFB9204008FA782 /* Resources */ = { 304 | isa = PBXResourcesBuildPhase; 305 | buildActionMask = 2147483647; 306 | files = ( 307 | 235C93991CCE2CB700E67605 /* Main.storyboard in Resources */, 308 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 309 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | 607FACE31AFB9204008FA782 /* Resources */ = { 314 | isa = PBXResourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | /* End PBXResourcesBuildPhase section */ 321 | 322 | /* Begin PBXShellScriptBuildPhase section */ 323 | 1796B3AB299F80B75A6FCFD4 /* [CP] Copy Pods Resources */ = { 324 | isa = PBXShellScriptBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | ); 328 | inputPaths = ( 329 | ); 330 | name = "[CP] Copy Pods Resources"; 331 | outputPaths = ( 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | shellPath = /bin/sh; 335 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RxMVC_Example/Pods-RxMVC_Example-resources.sh\"\n"; 336 | showEnvVarsInLog = 0; 337 | }; 338 | 89842A207F22DAE587BD45FC /* [CP] Embed Pods Frameworks */ = { 339 | isa = PBXShellScriptBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | ); 343 | inputPaths = ( 344 | ); 345 | name = "[CP] Embed Pods Frameworks"; 346 | outputPaths = ( 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | shellPath = /bin/sh; 350 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RxMVC_Example/Pods-RxMVC_Example-frameworks.sh\"\n"; 351 | showEnvVarsInLog = 0; 352 | }; 353 | A341C09CE3DD2E385D069310 /* [CP] Check Pods Manifest.lock */ = { 354 | isa = PBXShellScriptBuildPhase; 355 | buildActionMask = 2147483647; 356 | files = ( 357 | ); 358 | inputPaths = ( 359 | ); 360 | name = "[CP] Check Pods Manifest.lock"; 361 | outputPaths = ( 362 | ); 363 | runOnlyForDeploymentPostprocessing = 0; 364 | shellPath = /bin/sh; 365 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 366 | showEnvVarsInLog = 0; 367 | }; 368 | A5A4C35C630FEB284860B5C4 /* [CP] Check Pods Manifest.lock */ = { 369 | isa = PBXShellScriptBuildPhase; 370 | buildActionMask = 2147483647; 371 | files = ( 372 | ); 373 | inputPaths = ( 374 | ); 375 | name = "[CP] Check Pods Manifest.lock"; 376 | outputPaths = ( 377 | ); 378 | runOnlyForDeploymentPostprocessing = 0; 379 | shellPath = /bin/sh; 380 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 381 | showEnvVarsInLog = 0; 382 | }; 383 | BF60312CC157BC0A3383AFDE /* [CP] Embed Pods Frameworks */ = { 384 | isa = PBXShellScriptBuildPhase; 385 | buildActionMask = 2147483647; 386 | files = ( 387 | ); 388 | inputPaths = ( 389 | ); 390 | name = "[CP] Embed Pods Frameworks"; 391 | outputPaths = ( 392 | ); 393 | runOnlyForDeploymentPostprocessing = 0; 394 | shellPath = /bin/sh; 395 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RxMVC_Tests/Pods-RxMVC_Tests-frameworks.sh\"\n"; 396 | showEnvVarsInLog = 0; 397 | }; 398 | F5828CE1B97F891CD5F0E59F /* [CP] Copy Pods Resources */ = { 399 | isa = PBXShellScriptBuildPhase; 400 | buildActionMask = 2147483647; 401 | files = ( 402 | ); 403 | inputPaths = ( 404 | ); 405 | name = "[CP] Copy Pods Resources"; 406 | outputPaths = ( 407 | ); 408 | runOnlyForDeploymentPostprocessing = 0; 409 | shellPath = /bin/sh; 410 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RxMVC_Tests/Pods-RxMVC_Tests-resources.sh\"\n"; 411 | showEnvVarsInLog = 0; 412 | }; 413 | /* End PBXShellScriptBuildPhase section */ 414 | 415 | /* Begin PBXSourcesBuildPhase section */ 416 | 607FACCC1AFB9204008FA782 /* Sources */ = { 417 | isa = PBXSourcesBuildPhase; 418 | buildActionMask = 2147483647; 419 | files = ( 420 | 5AB80BBF1D2840EB00C13E1E /* GitHubSearchTypes.swift in Sources */, 421 | 5AB80BBA1D28408000C13E1E /* GitHubSearchModel.swift in Sources */, 422 | 5AB80BBD1D28408F00C13E1E /* GitHubSearchController.swift in Sources */, 423 | 5AB80BB21D28402D00C13E1E /* GitHubSearchViewController.swift in Sources */, 424 | 5AB80BB41D28402D00C13E1E /* UpDownViewController.swift in Sources */, 425 | 235C93951CCE2CB700E67605 /* AppDelegate.swift in Sources */, 426 | 5AB80BB71D28403500C13E1E /* GitHubAPI.swift in Sources */, 427 | 5AF1FEF41D2D3F4F00018671 /* GitHubSearchView.swift in Sources */, 428 | 5AB80BB31D28402D00C13E1E /* MasterViewController.swift in Sources */, 429 | ); 430 | runOnlyForDeploymentPostprocessing = 0; 431 | }; 432 | 607FACE11AFB9204008FA782 /* Sources */ = { 433 | isa = PBXSourcesBuildPhase; 434 | buildActionMask = 2147483647; 435 | files = ( 436 | 5A7A266F1D30CB2900219E63 /* UpDownTest.swift in Sources */, 437 | 5A7A26711D30CB4A00219E63 /* ModelTest.swift in Sources */, 438 | ); 439 | runOnlyForDeploymentPostprocessing = 0; 440 | }; 441 | /* End PBXSourcesBuildPhase section */ 442 | 443 | /* Begin PBXTargetDependency section */ 444 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 445 | isa = PBXTargetDependency; 446 | target = 607FACCF1AFB9204008FA782 /* RxMVC_Example */; 447 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 448 | }; 449 | /* End PBXTargetDependency section */ 450 | 451 | /* Begin PBXVariantGroup section */ 452 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 453 | isa = PBXVariantGroup; 454 | children = ( 455 | 607FACDF1AFB9204008FA782 /* Base */, 456 | ); 457 | name = LaunchScreen.xib; 458 | sourceTree = ""; 459 | }; 460 | /* End PBXVariantGroup section */ 461 | 462 | /* Begin XCBuildConfiguration section */ 463 | 607FACED1AFB9204008FA782 /* Debug */ = { 464 | isa = XCBuildConfiguration; 465 | buildSettings = { 466 | ALWAYS_SEARCH_USER_PATHS = NO; 467 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 468 | CLANG_CXX_LIBRARY = "libc++"; 469 | CLANG_ENABLE_MODULES = YES; 470 | CLANG_ENABLE_OBJC_ARC = YES; 471 | CLANG_WARN_BOOL_CONVERSION = YES; 472 | CLANG_WARN_CONSTANT_CONVERSION = YES; 473 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 474 | CLANG_WARN_EMPTY_BODY = YES; 475 | CLANG_WARN_ENUM_CONVERSION = YES; 476 | CLANG_WARN_INFINITE_RECURSION = YES; 477 | CLANG_WARN_INT_CONVERSION = YES; 478 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 479 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 483 | COPY_PHASE_STRIP = NO; 484 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 485 | ENABLE_STRICT_OBJC_MSGSEND = YES; 486 | ENABLE_TESTABILITY = YES; 487 | GCC_C_LANGUAGE_STANDARD = gnu99; 488 | GCC_DYNAMIC_NO_PIC = NO; 489 | GCC_NO_COMMON_BLOCKS = YES; 490 | GCC_OPTIMIZATION_LEVEL = 0; 491 | GCC_PREPROCESSOR_DEFINITIONS = ( 492 | "DEBUG=1", 493 | "$(inherited)", 494 | ); 495 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 496 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 497 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 498 | GCC_WARN_UNDECLARED_SELECTOR = YES; 499 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 500 | GCC_WARN_UNUSED_FUNCTION = YES; 501 | GCC_WARN_UNUSED_VARIABLE = YES; 502 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 503 | MTL_ENABLE_DEBUG_INFO = YES; 504 | ONLY_ACTIVE_ARCH = YES; 505 | SDKROOT = iphoneos; 506 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 507 | SWIFT_VERSION = 3.0; 508 | }; 509 | name = Debug; 510 | }; 511 | 607FACEE1AFB9204008FA782 /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ALWAYS_SEARCH_USER_PATHS = NO; 515 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 516 | CLANG_CXX_LIBRARY = "libc++"; 517 | CLANG_ENABLE_MODULES = YES; 518 | CLANG_ENABLE_OBJC_ARC = YES; 519 | CLANG_WARN_BOOL_CONVERSION = YES; 520 | CLANG_WARN_CONSTANT_CONVERSION = YES; 521 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 522 | CLANG_WARN_EMPTY_BODY = YES; 523 | CLANG_WARN_ENUM_CONVERSION = YES; 524 | CLANG_WARN_INFINITE_RECURSION = YES; 525 | CLANG_WARN_INT_CONVERSION = YES; 526 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 527 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 528 | CLANG_WARN_UNREACHABLE_CODE = YES; 529 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 530 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 531 | COPY_PHASE_STRIP = NO; 532 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 533 | ENABLE_NS_ASSERTIONS = NO; 534 | ENABLE_STRICT_OBJC_MSGSEND = YES; 535 | GCC_C_LANGUAGE_STANDARD = gnu99; 536 | GCC_NO_COMMON_BLOCKS = YES; 537 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 538 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 539 | GCC_WARN_UNDECLARED_SELECTOR = YES; 540 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 541 | GCC_WARN_UNUSED_FUNCTION = YES; 542 | GCC_WARN_UNUSED_VARIABLE = YES; 543 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 544 | MTL_ENABLE_DEBUG_INFO = NO; 545 | SDKROOT = iphoneos; 546 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 547 | SWIFT_VERSION = 3.0; 548 | VALIDATE_PRODUCT = YES; 549 | }; 550 | name = Release; 551 | }; 552 | 607FACF01AFB9204008FA782 /* Debug */ = { 553 | isa = XCBuildConfiguration; 554 | baseConfigurationReference = 64F2E66B3CFB98E9E4EF9350 /* Pods-RxMVC_Example.debug.xcconfig */; 555 | buildSettings = { 556 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 557 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 558 | ENABLE_BITCODE = YES; 559 | INFOPLIST_FILE = RxMVC/Info.plist; 560 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 561 | MODULE_NAME = ExampleApp; 562 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 563 | PRODUCT_NAME = "$(TARGET_NAME)"; 564 | SWIFT_VERSION = 3.0; 565 | }; 566 | name = Debug; 567 | }; 568 | 607FACF11AFB9204008FA782 /* Release */ = { 569 | isa = XCBuildConfiguration; 570 | baseConfigurationReference = BC0A58DAA3BC59E6B0BEB187 /* Pods-RxMVC_Example.release.xcconfig */; 571 | buildSettings = { 572 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 573 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 574 | ENABLE_BITCODE = YES; 575 | INFOPLIST_FILE = RxMVC/Info.plist; 576 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 577 | MODULE_NAME = ExampleApp; 578 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | SWIFT_VERSION = 3.0; 581 | }; 582 | name = Release; 583 | }; 584 | 607FACF31AFB9204008FA782 /* Debug */ = { 585 | isa = XCBuildConfiguration; 586 | baseConfigurationReference = E2721FBB958801016129AB50 /* Pods-RxMVC_Tests.debug.xcconfig */; 587 | buildSettings = { 588 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 589 | BUNDLE_LOADER = "$(TEST_HOST)"; 590 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 591 | GCC_PREPROCESSOR_DEFINITIONS = ( 592 | "DEBUG=1", 593 | "$(inherited)", 594 | ); 595 | INFOPLIST_FILE = Tests/Info.plist; 596 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 597 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 598 | PRODUCT_NAME = "$(TARGET_NAME)"; 599 | SWIFT_VERSION = 3.0; 600 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxMVC_Example.app/RxMVC_Example"; 601 | }; 602 | name = Debug; 603 | }; 604 | 607FACF41AFB9204008FA782 /* Release */ = { 605 | isa = XCBuildConfiguration; 606 | baseConfigurationReference = FC481AAF4732B6F4A2A15EEC /* Pods-RxMVC_Tests.release.xcconfig */; 607 | buildSettings = { 608 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 609 | BUNDLE_LOADER = "$(TEST_HOST)"; 610 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 611 | INFOPLIST_FILE = Tests/Info.plist; 612 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 613 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 614 | PRODUCT_NAME = "$(TARGET_NAME)"; 615 | SWIFT_VERSION = 3.0; 616 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxMVC_Example.app/RxMVC_Example"; 617 | }; 618 | name = Release; 619 | }; 620 | /* End XCBuildConfiguration section */ 621 | 622 | /* Begin XCConfigurationList section */ 623 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "RxMVC" */ = { 624 | isa = XCConfigurationList; 625 | buildConfigurations = ( 626 | 607FACED1AFB9204008FA782 /* Debug */, 627 | 607FACEE1AFB9204008FA782 /* Release */, 628 | ); 629 | defaultConfigurationIsVisible = 0; 630 | defaultConfigurationName = Release; 631 | }; 632 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxMVC_Example" */ = { 633 | isa = XCConfigurationList; 634 | buildConfigurations = ( 635 | 607FACF01AFB9204008FA782 /* Debug */, 636 | 607FACF11AFB9204008FA782 /* Release */, 637 | ); 638 | defaultConfigurationIsVisible = 0; 639 | defaultConfigurationName = Release; 640 | }; 641 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxMVC_Tests" */ = { 642 | isa = XCConfigurationList; 643 | buildConfigurations = ( 644 | 607FACF31AFB9204008FA782 /* Debug */, 645 | 607FACF41AFB9204008FA782 /* Release */, 646 | ); 647 | defaultConfigurationIsVisible = 0; 648 | defaultConfigurationName = Release; 649 | }; 650 | /* End XCConfigurationList section */ 651 | }; 652 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 653 | } 654 | -------------------------------------------------------------------------------- /Example/RxMVC.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RxMVC.xcodeproj/xcshareddata/xcschemes/RxMVC-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/RxMVC.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/RxMVC/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Examples 4 | // 5 | // Created by 최건우 on 2016. 4. 22.. 6 | // Copyright © 2016년 Choi Geonu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 16 | return true 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Example/RxMVC/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Example/RxMVC/Images.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 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Example/RxMVC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/RxMVC/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 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 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 115 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 169 | 176 | 183 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /Example/RxMVC/Utils/GitHubAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubAPI.swift 3 | // RxMVC 4 | // 5 | // Simple API interface for GitHub. 6 | // 7 | // Copyright © 2016년 CocoaPods. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import RxSwift 12 | import Alamofire 13 | import RxAlamofire 14 | 15 | /** 16 | Parsed GitHub respository. 17 | */ 18 | struct Repository { 19 | let name: String 20 | let fullName: String 21 | let htmlURL: String 22 | } 23 | 24 | public struct GitHubAPI { 25 | let manager: SessionManager 26 | 27 | func searchRepo(key: String) -> Observable<[Repository]> { 28 | let URL = Foundation.URL(string: "https://api.github.com/search/repositories")! 29 | return manager.rx.json(.get, URL, parameters: ["q": key]).map { data in 30 | if let data = data as? Dictionary { 31 | let items = data["items"] 32 | if let items = items as? [Dictionary] { 33 | return items.map{ item in 34 | let name = item["name"]?.description ?? "" 35 | let fullName = item["full_name"]?.description ?? "" 36 | let htmlURL = item["html_url"]?.description ?? "" 37 | return Repository(name: name, fullName: fullName, htmlURL: htmlURL) 38 | } 39 | } 40 | } 41 | return [] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/RxMVC/ViewControllers/GitHubSearchController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchController.swift 3 | // RxMVC 4 | // 5 | // GitHubSearch's asynchronous controller. 6 | // 7 | // Copyright © 2016년 CocoaPods. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import RxSwift 12 | import RxCocoa 13 | import Alamofire 14 | import RxMVC 15 | 16 | 17 | protocol GitHubSearchControllerDelegate: class { 18 | func openURL(url: URL) 19 | } 20 | 21 | struct GitHubSearchController: FlatMapController { 22 | typealias Event = GitHubSearchEvent 23 | typealias Action = GitHubSearchAction 24 | 25 | var flatMapType = FlatMapType.latest 26 | 27 | weak var delegate: GitHubSearchControllerDelegate? 28 | 29 | init(delegate: GitHubSearchControllerDelegate) { 30 | self.delegate = delegate 31 | } 32 | 33 | func flatMapToAction(event: Event) -> Observable { 34 | switch event { 35 | case .changeSearchText(let text): 36 | let query = text.trimmingCharacters(in: CharacterSet.whitespaces) 37 | if query == "" { 38 | return Observable.of(Action.updateQuery(nil), Action.updateRepositories([])) 39 | } else { 40 | return Observable.just(Action.updateQuery(query)).concat( 41 | GitHubAPI(manager: SessionManager.default).searchRepo(key: query).map { repositories in 42 | return Action.updateRepositories(repositories) 43 | }.asDriver(onErrorRecover: { (error) -> Driver in 44 | return Driver.just(Action.repositoriesError(error)) 45 | })) 46 | } 47 | case .selectRepository(let repository): 48 | if let url = URL(string: repository.htmlURL) { 49 | self.delegate?.openURL(url: url) 50 | } 51 | return Observable.empty() 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Example/RxMVC/ViewControllers/GitHubSearchModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchModel.swift 3 | // RxMVC 4 | // 5 | // GitHubSearch's model 6 | // 7 | // Copyright © 2016년 CocoaPods. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import RxMVC 12 | 13 | struct GitHubSearchModel: ReducerModel { 14 | typealias State = GitHubSearchState 15 | typealias Action = GitHubSearchAction 16 | 17 | let initialState = GitHubSearchState(query: nil, repositories: RepositoriesState.some([])) 18 | 19 | func reduce(state: State, with action: Action) -> State { 20 | switch action { 21 | case .updateQuery(let query): 22 | return State(query: query, repositories: state.repositories) 23 | case .updateRepositories(let repositories): 24 | return State(query: state.query, repositories: RepositoriesState.some(repositories)) 25 | case .repositoriesError(let error): 26 | return State(query: state.query, repositories: RepositoriesState.error(error)) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/RxMVC/ViewControllers/GitHubSearchTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchTypes.swift 3 | // RxMVC 4 | // 5 | // Created by Owen Choi on 2016. 7. 3.. 6 | // Copyright © 2016년 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum GitHubSearchEvent { 12 | case changeSearchText(String) 13 | case selectRepository(Repository) 14 | } 15 | 16 | enum GitHubSearchAction { 17 | case updateQuery(String?) 18 | case updateRepositories([Repository]) 19 | case repositoriesError(Error) 20 | } 21 | 22 | enum RepositoriesState { 23 | case some([Repository]) 24 | case error(Error) 25 | } 26 | 27 | struct GitHubSearchState { 28 | let query: String? 29 | let repositories: RepositoriesState 30 | } 31 | -------------------------------------------------------------------------------- /Example/RxMVC/ViewControllers/GitHubSearchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchView.swift 3 | // RxMVC 4 | // 5 | // Created by Owen Choi on 2016. 7. 6.. 6 | // Copyright © 2016년 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | import RxMVC 13 | import Toaster 14 | 15 | struct GitHubSearchView: View, UserInteractable { 16 | typealias State = GitHubSearchState 17 | typealias Event = GitHubSearchEvent 18 | 19 | let searchTextField: UITextField 20 | let tableView: UITableView 21 | 22 | func interact() -> Observable { 23 | return Observable.from([ 24 | searchTextField.rx.textInput.text.asDriver().map{text in Event.changeSearchText(text)}.throttle(0.5).asObservable(), 25 | tableView.rx.modelSelected(Repository.self).map{repository in Event.selectRepository(repository)} 26 | ]).merge() 27 | } 28 | 29 | func update(stateStream: Observable) -> Disposable { 30 | return CompositeDisposable(disposables: [ 31 | stateStream.map{state in state.query ?? ""}.distinctUntilChanged().bindTo(searchTextField.rx.textInput.text), 32 | stateStream 33 | .map {state -> [Repository] in 34 | switch(state.repositories) { 35 | case .some(let items): 36 | return items as Array 37 | case .error: 38 | return [] 39 | } 40 | } 41 | .bindTo(tableView.rx.items(cellIdentifier: "Cell")) { (row, element, cell) in 42 | cell.textLabel?.text = element.name 43 | cell.detailTextLabel?.text = element.fullName 44 | }, 45 | stateStream 46 | .map {state -> Error? in 47 | switch (state.repositories) { 48 | case .error(let error): 49 | return error 50 | default: 51 | return nil 52 | } 53 | } 54 | .asDriver(onErrorJustReturn: nil) 55 | .filter { error in error != nil } 56 | .map { error in error! } 57 | .throttle(1.0) 58 | .asObservable() 59 | .subscribe(onNext: {error in 60 | let error = error as NSError 61 | Toast(text: error.localizedDescription).show() 62 | })]) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/RxMVC/ViewControllers/GitHubSearchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchViewController.swift 3 | // RxMVC 4 | // 5 | // Asynchronous networking example. 6 | // Searchs repositories from GitHub and renders to tableview. 7 | // And it is also example of dividing M-V-C into multiple files. 8 | // 9 | // Copyright © 2016년 CocoaPods. All rights reserved. 10 | // 11 | 12 | import UIKit 13 | import RxSwift 14 | import RxMVC 15 | 16 | class GitHubSearchViewController: UIViewController, GitHubSearchControllerDelegate { 17 | @IBOutlet weak var searchTextField: UITextField! 18 | @IBOutlet weak var tableView: UITableView! 19 | 20 | var disposeBag = DisposeBag() 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | let model = GitHubSearchModel() 25 | let view = GitHubSearchView(searchTextField: searchTextField, tableView: tableView) 26 | let controller = GitHubSearchController(delegate: self) 27 | combineModel(model, withView: view, controller: controller).addDisposableTo(disposeBag) 28 | } 29 | 30 | override func didReceiveMemoryWarning() { 31 | super.didReceiveMemoryWarning() 32 | } 33 | 34 | // MARK: - Controller delegate functions 35 | 36 | func openURL(url: URL) { 37 | UIApplication.shared.openURL(url) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Example/RxMVC/ViewControllers/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // Examples 4 | // 5 | // Entry point of application. 6 | // It shows that how controller treat actions that can not be done without UIViewController. 7 | // 8 | // Copyright © 2016년 Choi Geonu. All rights reserved. 9 | // 10 | 11 | import UIKit 12 | import RxSwift 13 | import RxMVC 14 | 15 | enum MasterEvent { 16 | case clickItem(item: NameAndSegue) 17 | } 18 | 19 | enum MasterAction { 20 | 21 | } 22 | 23 | struct NameAndSegue { 24 | let name: String 25 | let segue: String 26 | } 27 | 28 | struct MasterModel: ConstantModel { 29 | typealias Action = MasterAction 30 | typealias State = [NameAndSegue] 31 | 32 | let state: [NameAndSegue] = [ 33 | NameAndSegue(name: "Up & Down", segue: "ShowUpDown"), 34 | NameAndSegue(name: "GitHub Search", segue: "ShowGitHubSearch") 35 | ] 36 | } 37 | 38 | protocol MasterControllerDelegate: class { 39 | func performSelectedSegue(_ segue: String) 40 | } 41 | 42 | struct MasterControlelr: FlatMapController { 43 | typealias Event = MasterEvent 44 | typealias Action = MasterAction 45 | 46 | weak var delegate: MasterControllerDelegate? 47 | 48 | init(delegate: MasterControllerDelegate) { 49 | self.delegate = delegate 50 | } 51 | 52 | 53 | func flatMapToAction(event: Event) -> Observable { 54 | switch event { 55 | case .clickItem(let item): 56 | self.delegate?.performSelectedSegue(item.segue) 57 | } 58 | return Observable.empty() 59 | } 60 | } 61 | 62 | struct MasterView: View { 63 | typealias State = [NameAndSegue] 64 | let tableView: UITableView 65 | 66 | func update(stateStream: Observable) -> Disposable { 67 | return CompositeDisposable(disposables: [ 68 | stateStream.bindTo(tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in 69 | cell.textLabel?.text = element.name 70 | }]) 71 | } 72 | } 73 | 74 | struct MasterUserInteractable: UserInteractable { 75 | typealias Event = MasterEvent 76 | let tableView: UITableView 77 | 78 | func interact() -> Observable { 79 | return Observable.from([ 80 | tableView.rx.modelSelected(NameAndSegue.self).map({vc in MasterEvent.clickItem(item: vc)}) 81 | ]).merge() 82 | } 83 | } 84 | 85 | class MasterViewController: UITableViewController, UISplitViewControllerDelegate, MasterControllerDelegate { 86 | var disposeBag = DisposeBag() 87 | 88 | var selectedViewController: UIViewController? = nil 89 | 90 | override func viewDidLoad() { 91 | super.viewDidLoad() 92 | 93 | self.splitViewController?.delegate = self 94 | // Set initial detail view controller 95 | tableView.delegate = nil 96 | tableView.dataSource = nil 97 | 98 | let model = MasterModel() 99 | let view = MasterView(tableView: tableView) 100 | let controller = MasterControlelr(delegate: self) 101 | let userInteractable = MasterUserInteractable(tableView: tableView) 102 | combineModel(model, withView: view, controller: controller, andUserInteractable: userInteractable) 103 | .addDisposableTo(disposeBag) 104 | } 105 | 106 | override func viewWillAppear(_ animated: Bool) { 107 | self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed 108 | super.viewWillAppear(animated) 109 | } 110 | 111 | override func didReceiveMemoryWarning() { 112 | super.didReceiveMemoryWarning() 113 | // Dispose of any resources that can be recreated. 114 | } 115 | 116 | // MARK: - Controller delegate functions 117 | func performSelectedSegue(_ segue: String) { 118 | performSegue(withIdentifier: segue, sender: self) 119 | } 120 | 121 | // MARK: - Split view 122 | 123 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { 124 | guard let _ = selectedViewController else { 125 | return true 126 | } 127 | return false 128 | } 129 | } 130 | 131 | -------------------------------------------------------------------------------- /Example/RxMVC/ViewControllers/UpDownViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpDownViewController.swift 3 | // Examples 4 | // 5 | // The most simple example of RxMVC 6 | // Just increases/decreases a number. 7 | // 8 | // Copyright © 2016년 Choi Geonu. All rights reserved. 9 | // 10 | 11 | import UIKit 12 | import RxSwift 13 | import RxCocoa 14 | import RxMVC 15 | 16 | enum UpDownEvent { 17 | case clickUp 18 | case clickDown 19 | case clickReset 20 | } 21 | 22 | enum UpDownAction { 23 | case increase 24 | case decrease 25 | case reset 26 | } 27 | 28 | struct UpDownModel: ReducerModel { 29 | typealias Action = UpDownAction 30 | typealias State = Int 31 | 32 | let initialState = 0 33 | func reduce(state: State, with action: Action) -> State { 34 | switch action { 35 | case .increase: 36 | return state + 1 37 | case .decrease: 38 | return state - 1 39 | case .reset: 40 | return initialState 41 | } 42 | } 43 | } 44 | 45 | struct UpDownView: View, UserInteractable { 46 | typealias State = Int 47 | typealias Event = UpDownEvent 48 | 49 | let countLabel: UILabel 50 | let upButton: UIButton 51 | let downButton: UIButton 52 | let resetButton: UIButton 53 | 54 | func update(stateStream: Observable) -> Disposable { 55 | return stateStream.subscribe(onNext: { (number) in 56 | self.countLabel.text = "\(number)" 57 | }) 58 | } 59 | 60 | func interact() -> Observable { 61 | return Observable.from([ 62 | self.upButton.rx.tap.map{_ in Event.clickUp}, 63 | self.downButton.rx.tap.map{_ in Event.clickDown}, 64 | self.resetButton.rx.tap.map{_ in Event.clickReset}, 65 | ]).merge() 66 | } 67 | } 68 | 69 | struct UpDownController: MapController { 70 | typealias Event = UpDownEvent 71 | typealias Action = UpDownAction 72 | 73 | func mapToAction(event: Event) -> Action { 74 | switch event { 75 | case .clickUp: 76 | return Action.increase 77 | case .clickDown: 78 | return Action.decrease 79 | case .clickReset: 80 | return Action.reset 81 | } 82 | } 83 | } 84 | 85 | class UpDownViewController: UIViewController { 86 | @IBOutlet weak var countLabel: UILabel! 87 | @IBOutlet weak var upButton: UIButton! 88 | @IBOutlet weak var downButton: UIButton! 89 | @IBOutlet weak var resetButton: UIButton! 90 | 91 | var disposeBag = DisposeBag() 92 | 93 | override func viewDidLoad() { 94 | super.viewDidLoad() 95 | let model = UpDownModel() 96 | let view = UpDownView(countLabel: countLabel, 97 | upButton: upButton, 98 | downButton: downButton, 99 | resetButton: resetButton) 100 | let controller = UpDownController() 101 | combineModel(model, withView: view, controller: controller).addDisposableTo(disposeBag) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Example/Tests/Example/UpDownTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpDownTest.swift 3 | // RxMVC 4 | // 5 | 6 | import XCTest 7 | import RxSwift 8 | import RxBlocking 9 | import RxTests 10 | import RxMVC 11 | 12 | @testable import RxMVC_Example 13 | 14 | class UpDownTest: XCTestCase { 15 | func testModel() { 16 | let model = UpDownModel() 17 | let scheduler = TestScheduler(initialClock: 0) 18 | let xs = scheduler.createHotObservable([ 19 | next(210, UpDownAction.increase), 20 | next(220, UpDownAction.increase), 21 | next(230, UpDownAction.increase), 22 | next(240, UpDownAction.decrease), 23 | next(250, UpDownAction.decrease), 24 | next(260, UpDownAction.reset), 25 | next(270, UpDownAction.increase), 26 | completed(300) 27 | ]) 28 | let res = scheduler.start { model.manipulate(actionStream: xs.asObservable()) } 29 | let expected = [ 30 | next(200, 0), 31 | next(210, 1), 32 | next(220, 2), 33 | next(230, 3), 34 | next(240, 2), 35 | next(250, 1), 36 | next(260, 0), 37 | next(270, 1), 38 | completed(300) 39 | ] 40 | XCTAssertEqual(res.events, expected) 41 | } 42 | 43 | func testController() { 44 | let controller = UpDownController() 45 | let scheduler = TestScheduler(initialClock: 0) 46 | let xs = scheduler.createHotObservable([ 47 | next(210, UpDownEvent.clickUp), 48 | next(220, UpDownEvent.clickDown), 49 | next(230, UpDownEvent.clickReset), 50 | completed(300) 51 | ]) 52 | let res = scheduler.start { controller.use(eventStream: xs.asObservable()) } 53 | let expected = [ 54 | next(210, UpDownAction.increase), 55 | next(220, UpDownAction.decrease), 56 | next(230, UpDownAction.reset), 57 | completed(300) 58 | ] 59 | XCTAssertEqual(res.events, expected) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/ModelTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelTest.swift 3 | // RxMVC 4 | // 5 | // Test of model utilities. 6 | // 7 | 8 | import XCTest 9 | import RxTests 10 | 11 | @testable import RxMVC 12 | 13 | struct TestModel: ReducerModel { 14 | enum Action { 15 | case increase 16 | } 17 | typealias State = Int 18 | 19 | let initialState = 1 20 | func reduce(state: State, with action: Action) -> State { 21 | switch action { 22 | case .increase: 23 | return state + 1 24 | } 25 | } 26 | } 27 | 28 | class ModelTest: XCTestCase { 29 | func testReducerModel() { 30 | let model = TestModel() 31 | let scheduler = TestScheduler(initialClock: 0) 32 | let xs = scheduler.createHotObservable([ 33 | next(210, TestModel.Action.increase), 34 | next(220, TestModel.Action.increase), 35 | next(230, TestModel.Action.increase), 36 | completed(300) 37 | ]) 38 | let res = scheduler.start { model.manipulate(actionStream: xs.asObservable()) } 39 | let expected = [ 40 | next(200, 1), 41 | next(210, 2), 42 | next(220, 3), 43 | next(230, 4), 44 | completed(300) 45 | ] 46 | XCTAssertEqual(res.events, expected) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.1' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (5.0.0) 5 | concurrent-ruby (~> 1.0, >= 1.0.2) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | tzinfo (~> 1.1) 9 | claide (1.0.0) 10 | cocoapods (1.0.1) 11 | activesupport (>= 4.0.2) 12 | claide (>= 1.0.0, < 2.0) 13 | cocoapods-core (= 1.0.1) 14 | cocoapods-deintegrate (>= 1.0.0, < 2.0) 15 | cocoapods-downloader (>= 1.0.0, < 2.0) 16 | cocoapods-plugins (>= 1.0.0, < 2.0) 17 | cocoapods-search (>= 1.0.0, < 2.0) 18 | cocoapods-stats (>= 1.0.0, < 2.0) 19 | cocoapods-trunk (>= 1.0.0, < 2.0) 20 | cocoapods-try (>= 1.0.0, < 2.0) 21 | colored (~> 1.2) 22 | escape (~> 0.0.4) 23 | fourflusher (~> 0.3.0) 24 | molinillo (~> 0.4.5) 25 | nap (~> 1.0) 26 | xcodeproj (>= 1.1.0, < 2.0) 27 | cocoapods-core (1.0.1) 28 | activesupport (>= 4.0.2) 29 | fuzzy_match (~> 2.0.4) 30 | nap (~> 1.0) 31 | cocoapods-deintegrate (1.0.0) 32 | cocoapods-downloader (1.0.1) 33 | cocoapods-plugins (1.0.0) 34 | nap 35 | cocoapods-search (1.0.0) 36 | cocoapods-stats (1.0.0) 37 | cocoapods-trunk (1.0.0) 38 | nap (>= 0.8, < 2.0) 39 | netrc (= 0.7.8) 40 | cocoapods-try (1.0.0) 41 | colored (1.2) 42 | concurrent-ruby (1.0.2) 43 | escape (0.0.4) 44 | fourflusher (0.3.2) 45 | fuzzy_match (2.0.4) 46 | i18n (0.7.0) 47 | minitest (5.9.0) 48 | molinillo (0.4.5) 49 | nap (1.1.0) 50 | netrc (0.7.8) 51 | thread_safe (0.3.5) 52 | tzinfo (1.2.2) 53 | thread_safe (~> 0.1) 54 | xcodeproj (1.1.0) 55 | activesupport (>= 3) 56 | claide (>= 1.0.0, < 2.0) 57 | colored (~> 1.2) 58 | 59 | PLATFORMS 60 | ruby 61 | 62 | DEPENDENCIES 63 | cocoapods (~> 1.0) 64 | 65 | BUNDLED WITH 66 | 1.12.4 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Choi Geonu <6566gun@gmail.com> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxMVC 2 | 3 | [![CI Status](http://img.shields.io/travis/Hardtack/RxMVC-Swift.svg?style=flat)](https://travis-ci.org/Hardtack/RxMVC-Swift) 4 | [![Version](https://img.shields.io/cocoapods/v/RxMVC.svg?style=flat)](http://cocoapods.org/pods/RxMVC) 5 | [![License](https://img.shields.io/cocoapods/l/RxMVC.svg?style=flat)](http://cocoapods.org/pods/RxMVC) 6 | [![Platform](https://img.shields.io/cocoapods/p/RxMVC.svg?style=flat)](http://cocoapods.org/pods/RxMVC) 7 | 8 | Model-View-Controller pattern with [RxSwift](https://github.com/ReactiveX/RxSwift). 9 | 10 | ## Documentation 11 | 12 | See [Wiki](https://github.com/Hardtack/RxMVC-Swift/wiki) 13 | 14 | ## Mode-View-Controller 15 | 16 | RxMVC supports to implement Model-View-Controller pattern with RxSwift like this: 17 | ![MVC in Wikipedia](https://upload.wikimedia.org/wikipedia/commons/a/a0/MVC-Process.svg) 18 | [_(from wikipedia)_](https://en.wikipedia.org/wiki/Model–view–controller) 19 | 20 | **Not this**: 21 | 22 | ![MVC in Wikipedia](./Resources/MVC_Cocoa.png) 23 | 24 | [_(from apple's document)_](https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html) 25 | 26 | I don't know what the original definition is. But RxMVC is targeted to upper definition. 27 | 28 | ## Usage 29 | 30 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 31 | -------------------------------------------------------------------------------- /Resources/MVC_Cocoa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hardtack/RxMVC-Swift/204ab52e5d0d6669d340ef3f7b10c92892a9bdde/Resources/MVC_Cocoa.png -------------------------------------------------------------------------------- /RxMVC.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RxMVC" 3 | s.version = "0.2.0-beta.2" 4 | s.summary = "Model-View-Controller pattern with RxSwift" 5 | s.description = <<-DESC 6 | Model-View-Controller pattern with RxSwift. 7 | DESC 8 | s.homepage = "https://github.com/Hardtack/RxMVC-Swift" 9 | s.license = 'MIT' 10 | s.author = { "Choi Geonu" => "6566gun@gmail.com" } 11 | s.source = { :git => "https://github.com/Hardtack/RxMVC-Swift.git", :tag => s.version.to_s } 12 | 13 | s.ios.deployment_target = '8.0' 14 | 15 | s.source_files = 'RxMVC/Classes/**/*' 16 | s.resource_bundles = { 17 | } 18 | 19 | s.dependency 'RxSwift', '~> 3.0.0-beta.1' 20 | s.dependency 'RxCocoa', '~> 3.0.0-beta.1' 21 | end 22 | -------------------------------------------------------------------------------- /RxMVC/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hardtack/RxMVC-Swift/204ab52e5d0d6669d340ef3f7b10c92892a9bdde/RxMVC/Assets/.gitkeep -------------------------------------------------------------------------------- /RxMVC/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hardtack/RxMVC-Swift/204ab52e5d0d6669d340ef3f7b10c92892a9bdde/RxMVC/Classes/.gitkeep -------------------------------------------------------------------------------- /RxMVC/Classes/Controller.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.swift 3 | // RxMVC 4 | // 5 | // Created by 최건우 on 2016. 4. 22.. 6 | // Copyright © 2016년 Choi Geonu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | public protocol Controller { 13 | associatedtype Event // Event type 14 | associatedtype Action // Action type 15 | 16 | func use(eventStream: Observable) -> Observable 17 | } 18 | 19 | public protocol MapController: Controller { 20 | associatedtype Event // Event type 21 | associatedtype Action // Action type 22 | 23 | func mapToAction(event: Event) -> Action 24 | } 25 | 26 | public extension MapController { 27 | func use(eventStream: Observable) -> Observable { 28 | return eventStream.map({ (e) in 29 | return self.mapToAction(event: e) 30 | }) 31 | } 32 | } 33 | 34 | public enum FlatMapType { 35 | case normal 36 | case first 37 | case latest 38 | } 39 | 40 | public protocol FlatMapController: Controller { 41 | associatedtype Event // Event type 42 | associatedtype Action // Action type 43 | 44 | var flatMapType: FlatMapType {get} 45 | 46 | func flatMapToAction(event: Event) -> Observable 47 | } 48 | 49 | public extension FlatMapController { 50 | var flatMapType: FlatMapType { 51 | get { 52 | return FlatMapType.normal 53 | } 54 | } 55 | 56 | func use(eventStream: Observable) -> Observable { 57 | switch flatMapType { 58 | case .normal: 59 | return eventStream.flatMap({ (e) in 60 | return self.flatMapToAction(event: e) 61 | }) 62 | case .first: 63 | return eventStream.flatMapFirst({ (e) in 64 | return self.flatMapToAction(event: e) 65 | }) 66 | case .latest: 67 | return eventStream.flatMapLatest({ (e) in 68 | return self.flatMapToAction(event: e) 69 | }) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /RxMVC/Classes/MVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MVC.swift 3 | // RxMVC 4 | // 5 | // Created by 최건우 on 2016. 4. 22.. 6 | // Copyright © 2016년 Choi Geonu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | public func combineModel 13 | 14 | (_ model: M, withView view: V, controller: C, andUserInteractable userInteractable: U) -> Disposable where 15 | M.State == V.State, U.Event == C.Event, C.Action == M.Action { 16 | let eventStream = userInteractable.interact() 17 | let actionStream = controller.use(eventStream: eventStream) 18 | let stateStream = model.manipulate(actionStream: actionStream) 19 | return view.update(stateStream: stateStream) 20 | } 21 | 22 | 23 | public func combineModel 24 | 25 | (_ model: M, withView view: V, controller: C) -> Disposable where 26 | V: UserInteractable, M.State == V.State, V.Event == C.Event, C.Action == M.Action { 27 | return combineModel(model, withView: view, controller: controller, andUserInteractable: view) 28 | } 29 | -------------------------------------------------------------------------------- /RxMVC/Classes/Model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Model.swift 3 | // RxMVC 4 | // 5 | // Created by 최건우 on 2016. 4. 22.. 6 | // Copyright © 2016년 Choi Geonu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | public protocol Model { 13 | associatedtype Action // Action type 14 | associatedtype State // State type 15 | 16 | func manipulate(actionStream: Observable) -> Observable 17 | } 18 | 19 | public protocol ConstantModel: Model { 20 | associatedtype Action // Action type 21 | associatedtype State // State type 22 | 23 | var state: State { get } 24 | } 25 | 26 | public extension ConstantModel { 27 | func manipulate(actionStream: Observable) -> Observable { 28 | return Observable.of(Observable.just(state), actionStream.map({ _ in self.state})).merge() 29 | } 30 | } 31 | 32 | public protocol ReducerModel: Model { 33 | associatedtype Action // Action type 34 | associatedtype State // State type 35 | 36 | var initialState: State { get } 37 | func reduce(state: State, with action: Action) -> State 38 | } 39 | 40 | public extension ReducerModel { 41 | func manipulate(actionStream: Observable) -> Observable { 42 | let initial = Observable.just(initialState) 43 | let reduced = actionStream.scan(initialState, accumulator: {state, action in 44 | return self.reduce(state: state, with: action) 45 | }) 46 | return initial.concat(reduced) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RxMVC/Classes/UserInteractable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInteractable.swift 3 | // RxMVC 4 | // 5 | // Created by 최건우 on 2016. 4. 27.. 6 | // Copyright © 2016년 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | public protocol UserInteractable { 13 | associatedtype Event // Event type 14 | 15 | func interact() -> Observable 16 | } -------------------------------------------------------------------------------- /RxMVC/Classes/View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View.swift 3 | // RxMVC 4 | // 5 | // Created by 최건우 on 2016. 4. 22.. 6 | // Copyright © 2016년 Choi Geonu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | 13 | public protocol View { 14 | associatedtype State // State type 15 | 16 | func update(stateStream: Observable) -> Disposable 17 | } 18 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------