├── .gitignore ├── MVCDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── MVCDemo ├── AppDelegate.swift ├── Components │ ├── Controllers │ │ └── WWDCAttendeesController.swift │ ├── Error │ │ └── Error.swift │ ├── Models │ │ └── Attendee.swift │ ├── Network │ │ ├── Connection.swift │ │ ├── MockedConnection.swift │ │ └── Resource.swift │ ├── Parser │ │ └── Parser.swift │ └── Result │ │ └── Result.swift ├── Resources │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Localizable.strings │ └── attendees.json ├── Supporting Files │ └── Info.plist └── UI │ ├── Generic UI │ ├── CellConfigurable.swift │ ├── TableView+Dequeuing.swift │ └── TableViewDataSource.swift │ ├── ViewControllers │ ├── AttendeeCell.swift │ ├── AttendeeCellController.swift │ ├── WWDCAttendeesUIController.swift │ └── WWDCAttendeesViewController.swift │ └── Views │ └── LoadingView.swift ├── MVCDemoTests ├── Info.plist └── MVCDemoTests.swift └── README.md /.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 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /MVCDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C70DF9531CFCF49400CFC0DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C70DF9521CFCF49400CFC0DA /* Localizable.strings */; }; 11 | C73B8DB21CFA6FEF007C73DA /* MVCDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DB11CFA6FEF007C73DA /* MVCDemoTests.swift */; }; 12 | C73B8DC81CFA70C6007C73DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DBC1CFA70C6007C73DA /* AppDelegate.swift */; }; 13 | C73B8DC91CFA70C6007C73DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C73B8DBF1CFA70C6007C73DA /* Assets.xcassets */; }; 14 | C73B8DD71CFA752C007C73DA /* WWDCAttendeesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DD61CFA752C007C73DA /* WWDCAttendeesController.swift */; }; 15 | C73B8DDA1CFA765F007C73DA /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DD91CFA765F007C73DA /* Connection.swift */; }; 16 | C73B8DDD1CFA7737007C73DA /* Attendee.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DDC1CFA7737007C73DA /* Attendee.swift */; }; 17 | C73B8DE01CFA77ED007C73DA /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DDF1CFA77ED007C73DA /* Error.swift */; }; 18 | C73B8DE31CFA7D4A007C73DA /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DE21CFA7D4A007C73DA /* Result.swift */; }; 19 | C73B8DE61CFA813F007C73DA /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DE51CFA813F007C73DA /* Parser.swift */; }; 20 | C73B8DE81CFA883B007C73DA /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DE71CFA883B007C73DA /* Resource.swift */; }; 21 | C73B8DEC1CFC619D007C73DA /* WWDCAttendeesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DEB1CFC619D007C73DA /* WWDCAttendeesViewController.swift */; }; 22 | C73B8DF11CFC692E007C73DA /* MockedConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DF01CFC692E007C73DA /* MockedConnection.swift */; }; 23 | C73B8DF31CFC6EB5007C73DA /* WWDCAttendeesUIController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DF21CFC6EB5007C73DA /* WWDCAttendeesUIController.swift */; }; 24 | C73B8DF81CFCABAB007C73DA /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DF71CFCABAB007C73DA /* TableViewDataSource.swift */; }; 25 | C73B8DFA1CFCABBF007C73DA /* TableView+Dequeuing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DF91CFCABBF007C73DA /* TableView+Dequeuing.swift */; }; 26 | C73B8DFC1CFCAC4F007C73DA /* AttendeeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DFB1CFCAC4F007C73DA /* AttendeeCell.swift */; }; 27 | C73B8DFE1CFCB049007C73DA /* CellConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DFD1CFCB049007C73DA /* CellConfigurable.swift */; }; 28 | C73B8E001CFCB1F3007C73DA /* AttendeeCellController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8DFF1CFCB1F3007C73DA /* AttendeeCellController.swift */; }; 29 | C73B8E031CFCB5E8007C73DA /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C73B8E021CFCB5E8007C73DA /* LoadingView.swift */; }; 30 | C73B8E051CFCB8B5007C73DA /* attendees.json in Resources */ = {isa = PBXBuildFile; fileRef = C73B8E041CFCB8B5007C73DA /* attendees.json */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | C73B8DAE1CFA6FEF007C73DA /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = C73B8D911CFA6FEF007C73DA /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = C73B8D981CFA6FEF007C73DA; 39 | remoteInfo = MVCDemo; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | C70DF9521CFCF49400CFC0DA /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; 45 | C73B8D991CFA6FEF007C73DA /* MVCDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MVCDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | C73B8DAD1CFA6FEF007C73DA /* MVCDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MVCDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | C73B8DB11CFA6FEF007C73DA /* MVCDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVCDemoTests.swift; sourceTree = ""; }; 48 | C73B8DB31CFA6FEF007C73DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | C73B8DBC1CFA70C6007C73DA /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | C73B8DBF1CFA70C6007C73DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | C73B8DC51CFA70C6007C73DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | C73B8DD61CFA752C007C73DA /* WWDCAttendeesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WWDCAttendeesController.swift; sourceTree = ""; }; 53 | C73B8DD91CFA765F007C73DA /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; 54 | C73B8DDC1CFA7737007C73DA /* Attendee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attendee.swift; sourceTree = ""; }; 55 | C73B8DDF1CFA77ED007C73DA /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 56 | C73B8DE21CFA7D4A007C73DA /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 57 | C73B8DE51CFA813F007C73DA /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; }; 58 | C73B8DE71CFA883B007C73DA /* Resource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resource.swift; sourceTree = ""; }; 59 | C73B8DEB1CFC619D007C73DA /* WWDCAttendeesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WWDCAttendeesViewController.swift; sourceTree = ""; }; 60 | C73B8DF01CFC692E007C73DA /* MockedConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockedConnection.swift; sourceTree = ""; }; 61 | C73B8DF21CFC6EB5007C73DA /* WWDCAttendeesUIController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WWDCAttendeesUIController.swift; sourceTree = ""; }; 62 | C73B8DF71CFCABAB007C73DA /* TableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = ""; }; 63 | C73B8DF91CFCABBF007C73DA /* TableView+Dequeuing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TableView+Dequeuing.swift"; sourceTree = ""; }; 64 | C73B8DFB1CFCAC4F007C73DA /* AttendeeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttendeeCell.swift; sourceTree = ""; }; 65 | C73B8DFD1CFCB049007C73DA /* CellConfigurable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellConfigurable.swift; sourceTree = ""; }; 66 | C73B8DFF1CFCB1F3007C73DA /* AttendeeCellController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttendeeCellController.swift; sourceTree = ""; }; 67 | C73B8E021CFCB5E8007C73DA /* LoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; 68 | C73B8E041CFCB8B5007C73DA /* attendees.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = attendees.json; sourceTree = ""; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | C73B8D961CFA6FEF007C73DA /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | C73B8DAA1CFA6FEF007C73DA /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | C73B8D901CFA6FEF007C73DA = { 90 | isa = PBXGroup; 91 | children = ( 92 | C73B8D9B1CFA6FEF007C73DA /* MVCDemo */, 93 | C73B8DB01CFA6FEF007C73DA /* MVCDemoTests */, 94 | C73B8D9A1CFA6FEF007C73DA /* Products */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | C73B8D9A1CFA6FEF007C73DA /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | C73B8D991CFA6FEF007C73DA /* MVCDemo.app */, 102 | C73B8DAD1CFA6FEF007C73DA /* MVCDemoTests.xctest */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | C73B8D9B1CFA6FEF007C73DA /* MVCDemo */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | C73B8DBC1CFA70C6007C73DA /* AppDelegate.swift */, 111 | C73B8DE91CFC619D007C73DA /* UI */, 112 | C73B8DBD1CFA70C6007C73DA /* Components */, 113 | C73B8DBE1CFA70C6007C73DA /* Resources */, 114 | C73B8DC41CFA70C6007C73DA /* Supporting Files */, 115 | ); 116 | path = MVCDemo; 117 | sourceTree = ""; 118 | }; 119 | C73B8DB01CFA6FEF007C73DA /* MVCDemoTests */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | C73B8DB11CFA6FEF007C73DA /* MVCDemoTests.swift */, 123 | C73B8DB31CFA6FEF007C73DA /* Info.plist */, 124 | ); 125 | path = MVCDemoTests; 126 | sourceTree = ""; 127 | }; 128 | C73B8DBD1CFA70C6007C73DA /* Components */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | C73B8DE41CFA7EE2007C73DA /* Parser */, 132 | C73B8DE11CFA7D45007C73DA /* Result */, 133 | C73B8DDE1CFA77E6007C73DA /* Error */, 134 | C73B8DDB1CFA772A007C73DA /* Models */, 135 | C73B8DD81CFA7644007C73DA /* Network */, 136 | C73B8DD51CFA7523007C73DA /* Controllers */, 137 | ); 138 | path = Components; 139 | sourceTree = ""; 140 | }; 141 | C73B8DBE1CFA70C6007C73DA /* Resources */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | C73B8DBF1CFA70C6007C73DA /* Assets.xcassets */, 145 | C73B8E041CFCB8B5007C73DA /* attendees.json */, 146 | C70DF9521CFCF49400CFC0DA /* Localizable.strings */, 147 | ); 148 | path = Resources; 149 | sourceTree = ""; 150 | }; 151 | C73B8DC41CFA70C6007C73DA /* Supporting Files */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | C73B8DC51CFA70C6007C73DA /* Info.plist */, 155 | ); 156 | path = "Supporting Files"; 157 | sourceTree = ""; 158 | }; 159 | C73B8DD51CFA7523007C73DA /* Controllers */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | C73B8DD61CFA752C007C73DA /* WWDCAttendeesController.swift */, 163 | ); 164 | path = Controllers; 165 | sourceTree = ""; 166 | }; 167 | C73B8DD81CFA7644007C73DA /* Network */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | C73B8DD91CFA765F007C73DA /* Connection.swift */, 171 | C73B8DF01CFC692E007C73DA /* MockedConnection.swift */, 172 | C73B8DE71CFA883B007C73DA /* Resource.swift */, 173 | ); 174 | path = Network; 175 | sourceTree = ""; 176 | }; 177 | C73B8DDB1CFA772A007C73DA /* Models */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | C73B8DDC1CFA7737007C73DA /* Attendee.swift */, 181 | ); 182 | path = Models; 183 | sourceTree = ""; 184 | }; 185 | C73B8DDE1CFA77E6007C73DA /* Error */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | C73B8DDF1CFA77ED007C73DA /* Error.swift */, 189 | ); 190 | path = Error; 191 | sourceTree = ""; 192 | }; 193 | C73B8DE11CFA7D45007C73DA /* Result */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | C73B8DE21CFA7D4A007C73DA /* Result.swift */, 197 | ); 198 | path = Result; 199 | sourceTree = ""; 200 | }; 201 | C73B8DE41CFA7EE2007C73DA /* Parser */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | C73B8DE51CFA813F007C73DA /* Parser.swift */, 205 | ); 206 | path = Parser; 207 | sourceTree = ""; 208 | }; 209 | C73B8DE91CFC619D007C73DA /* UI */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | C73B8DF61CFCAB9B007C73DA /* Generic UI */, 213 | C73B8E011CFCB5E1007C73DA /* Views */, 214 | C73B8DEA1CFC619D007C73DA /* ViewControllers */, 215 | ); 216 | path = UI; 217 | sourceTree = ""; 218 | }; 219 | C73B8DEA1CFC619D007C73DA /* ViewControllers */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | C73B8DEB1CFC619D007C73DA /* WWDCAttendeesViewController.swift */, 223 | C73B8DF21CFC6EB5007C73DA /* WWDCAttendeesUIController.swift */, 224 | C73B8DFB1CFCAC4F007C73DA /* AttendeeCell.swift */, 225 | C73B8DFF1CFCB1F3007C73DA /* AttendeeCellController.swift */, 226 | ); 227 | path = ViewControllers; 228 | sourceTree = ""; 229 | }; 230 | C73B8DF61CFCAB9B007C73DA /* Generic UI */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | C73B8DF71CFCABAB007C73DA /* TableViewDataSource.swift */, 234 | C73B8DF91CFCABBF007C73DA /* TableView+Dequeuing.swift */, 235 | C73B8DFD1CFCB049007C73DA /* CellConfigurable.swift */, 236 | ); 237 | path = "Generic UI"; 238 | sourceTree = ""; 239 | }; 240 | C73B8E011CFCB5E1007C73DA /* Views */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | C73B8E021CFCB5E8007C73DA /* LoadingView.swift */, 244 | ); 245 | path = Views; 246 | sourceTree = ""; 247 | }; 248 | /* End PBXGroup section */ 249 | 250 | /* Begin PBXNativeTarget section */ 251 | C73B8D981CFA6FEF007C73DA /* MVCDemo */ = { 252 | isa = PBXNativeTarget; 253 | buildConfigurationList = C73B8DB61CFA6FEF007C73DA /* Build configuration list for PBXNativeTarget "MVCDemo" */; 254 | buildPhases = ( 255 | C73B8D951CFA6FEF007C73DA /* Sources */, 256 | C73B8D961CFA6FEF007C73DA /* Frameworks */, 257 | C73B8D971CFA6FEF007C73DA /* Resources */, 258 | ); 259 | buildRules = ( 260 | ); 261 | dependencies = ( 262 | ); 263 | name = MVCDemo; 264 | productName = MVCDemo; 265 | productReference = C73B8D991CFA6FEF007C73DA /* MVCDemo.app */; 266 | productType = "com.apple.product-type.application"; 267 | }; 268 | C73B8DAC1CFA6FEF007C73DA /* MVCDemoTests */ = { 269 | isa = PBXNativeTarget; 270 | buildConfigurationList = C73B8DB91CFA6FEF007C73DA /* Build configuration list for PBXNativeTarget "MVCDemoTests" */; 271 | buildPhases = ( 272 | C73B8DA91CFA6FEF007C73DA /* Sources */, 273 | C73B8DAA1CFA6FEF007C73DA /* Frameworks */, 274 | C73B8DAB1CFA6FEF007C73DA /* Resources */, 275 | ); 276 | buildRules = ( 277 | ); 278 | dependencies = ( 279 | C73B8DAF1CFA6FEF007C73DA /* PBXTargetDependency */, 280 | ); 281 | name = MVCDemoTests; 282 | productName = MVCDemoTests; 283 | productReference = C73B8DAD1CFA6FEF007C73DA /* MVCDemoTests.xctest */; 284 | productType = "com.apple.product-type.bundle.unit-test"; 285 | }; 286 | /* End PBXNativeTarget section */ 287 | 288 | /* Begin PBXProject section */ 289 | C73B8D911CFA6FEF007C73DA /* Project object */ = { 290 | isa = PBXProject; 291 | attributes = { 292 | LastSwiftUpdateCheck = 0730; 293 | LastUpgradeCheck = 0730; 294 | ORGANIZATIONNAME = Razeware; 295 | TargetAttributes = { 296 | C73B8D981CFA6FEF007C73DA = { 297 | CreatedOnToolsVersion = 7.3.1; 298 | }; 299 | C73B8DAC1CFA6FEF007C73DA = { 300 | CreatedOnToolsVersion = 7.3.1; 301 | TestTargetID = C73B8D981CFA6FEF007C73DA; 302 | }; 303 | }; 304 | }; 305 | buildConfigurationList = C73B8D941CFA6FEF007C73DA /* Build configuration list for PBXProject "MVCDemo" */; 306 | compatibilityVersion = "Xcode 3.2"; 307 | developmentRegion = English; 308 | hasScannedForEncodings = 0; 309 | knownRegions = ( 310 | en, 311 | Base, 312 | ); 313 | mainGroup = C73B8D901CFA6FEF007C73DA; 314 | productRefGroup = C73B8D9A1CFA6FEF007C73DA /* Products */; 315 | projectDirPath = ""; 316 | projectRoot = ""; 317 | targets = ( 318 | C73B8D981CFA6FEF007C73DA /* MVCDemo */, 319 | C73B8DAC1CFA6FEF007C73DA /* MVCDemoTests */, 320 | ); 321 | }; 322 | /* End PBXProject section */ 323 | 324 | /* Begin PBXResourcesBuildPhase section */ 325 | C73B8D971CFA6FEF007C73DA /* Resources */ = { 326 | isa = PBXResourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | C70DF9531CFCF49400CFC0DA /* Localizable.strings in Resources */, 330 | C73B8DC91CFA70C6007C73DA /* Assets.xcassets in Resources */, 331 | C73B8E051CFCB8B5007C73DA /* attendees.json in Resources */, 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | C73B8DAB1CFA6FEF007C73DA /* Resources */ = { 336 | isa = PBXResourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | ); 340 | runOnlyForDeploymentPostprocessing = 0; 341 | }; 342 | /* End PBXResourcesBuildPhase section */ 343 | 344 | /* Begin PBXSourcesBuildPhase section */ 345 | C73B8D951CFA6FEF007C73DA /* Sources */ = { 346 | isa = PBXSourcesBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | C73B8DF31CFC6EB5007C73DA /* WWDCAttendeesUIController.swift in Sources */, 350 | C73B8DF11CFC692E007C73DA /* MockedConnection.swift in Sources */, 351 | C73B8DE81CFA883B007C73DA /* Resource.swift in Sources */, 352 | C73B8DEC1CFC619D007C73DA /* WWDCAttendeesViewController.swift in Sources */, 353 | C73B8E031CFCB5E8007C73DA /* LoadingView.swift in Sources */, 354 | C73B8DDD1CFA7737007C73DA /* Attendee.swift in Sources */, 355 | C73B8DD71CFA752C007C73DA /* WWDCAttendeesController.swift in Sources */, 356 | C73B8DF81CFCABAB007C73DA /* TableViewDataSource.swift in Sources */, 357 | C73B8DFA1CFCABBF007C73DA /* TableView+Dequeuing.swift in Sources */, 358 | C73B8DC81CFA70C6007C73DA /* AppDelegate.swift in Sources */, 359 | C73B8DE61CFA813F007C73DA /* Parser.swift in Sources */, 360 | C73B8DFE1CFCB049007C73DA /* CellConfigurable.swift in Sources */, 361 | C73B8DE31CFA7D4A007C73DA /* Result.swift in Sources */, 362 | C73B8E001CFCB1F3007C73DA /* AttendeeCellController.swift in Sources */, 363 | C73B8DE01CFA77ED007C73DA /* Error.swift in Sources */, 364 | C73B8DDA1CFA765F007C73DA /* Connection.swift in Sources */, 365 | C73B8DFC1CFCAC4F007C73DA /* AttendeeCell.swift in Sources */, 366 | ); 367 | runOnlyForDeploymentPostprocessing = 0; 368 | }; 369 | C73B8DA91CFA6FEF007C73DA /* Sources */ = { 370 | isa = PBXSourcesBuildPhase; 371 | buildActionMask = 2147483647; 372 | files = ( 373 | C73B8DB21CFA6FEF007C73DA /* MVCDemoTests.swift in Sources */, 374 | ); 375 | runOnlyForDeploymentPostprocessing = 0; 376 | }; 377 | /* End PBXSourcesBuildPhase section */ 378 | 379 | /* Begin PBXTargetDependency section */ 380 | C73B8DAF1CFA6FEF007C73DA /* PBXTargetDependency */ = { 381 | isa = PBXTargetDependency; 382 | target = C73B8D981CFA6FEF007C73DA /* MVCDemo */; 383 | targetProxy = C73B8DAE1CFA6FEF007C73DA /* PBXContainerItemProxy */; 384 | }; 385 | /* End PBXTargetDependency section */ 386 | 387 | /* Begin XCBuildConfiguration section */ 388 | C73B8DB41CFA6FEF007C73DA /* Debug */ = { 389 | isa = XCBuildConfiguration; 390 | buildSettings = { 391 | ALWAYS_SEARCH_USER_PATHS = NO; 392 | CLANG_ANALYZER_NONNULL = YES; 393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 394 | CLANG_CXX_LIBRARY = "libc++"; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | CLANG_WARN_BOOL_CONVERSION = YES; 398 | CLANG_WARN_CONSTANT_CONVERSION = YES; 399 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 400 | CLANG_WARN_EMPTY_BODY = YES; 401 | CLANG_WARN_ENUM_CONVERSION = YES; 402 | CLANG_WARN_INT_CONVERSION = YES; 403 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 404 | CLANG_WARN_UNREACHABLE_CODE = YES; 405 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 406 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 407 | COPY_PHASE_STRIP = NO; 408 | DEBUG_INFORMATION_FORMAT = dwarf; 409 | ENABLE_STRICT_OBJC_MSGSEND = YES; 410 | ENABLE_TESTABILITY = YES; 411 | GCC_C_LANGUAGE_STANDARD = gnu99; 412 | GCC_DYNAMIC_NO_PIC = NO; 413 | GCC_NO_COMMON_BLOCKS = YES; 414 | GCC_OPTIMIZATION_LEVEL = 0; 415 | GCC_PREPROCESSOR_DEFINITIONS = ( 416 | "DEBUG=1", 417 | "$(inherited)", 418 | ); 419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 421 | GCC_WARN_UNDECLARED_SELECTOR = YES; 422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 423 | GCC_WARN_UNUSED_FUNCTION = YES; 424 | GCC_WARN_UNUSED_VARIABLE = YES; 425 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 426 | MTL_ENABLE_DEBUG_INFO = YES; 427 | ONLY_ACTIVE_ARCH = YES; 428 | SDKROOT = iphoneos; 429 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 430 | TARGETED_DEVICE_FAMILY = "1,2"; 431 | }; 432 | name = Debug; 433 | }; 434 | C73B8DB51CFA6FEF007C73DA /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | ALWAYS_SEARCH_USER_PATHS = NO; 438 | CLANG_ANALYZER_NONNULL = YES; 439 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 440 | CLANG_CXX_LIBRARY = "libc++"; 441 | CLANG_ENABLE_MODULES = YES; 442 | CLANG_ENABLE_OBJC_ARC = YES; 443 | CLANG_WARN_BOOL_CONVERSION = YES; 444 | CLANG_WARN_CONSTANT_CONVERSION = YES; 445 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 446 | CLANG_WARN_EMPTY_BODY = YES; 447 | CLANG_WARN_ENUM_CONVERSION = YES; 448 | CLANG_WARN_INT_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_UNREACHABLE_CODE = YES; 451 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 452 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 453 | COPY_PHASE_STRIP = NO; 454 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 455 | ENABLE_NS_ASSERTIONS = NO; 456 | ENABLE_STRICT_OBJC_MSGSEND = YES; 457 | GCC_C_LANGUAGE_STANDARD = gnu99; 458 | GCC_NO_COMMON_BLOCKS = YES; 459 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 460 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 461 | GCC_WARN_UNDECLARED_SELECTOR = YES; 462 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 463 | GCC_WARN_UNUSED_FUNCTION = YES; 464 | GCC_WARN_UNUSED_VARIABLE = YES; 465 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 466 | MTL_ENABLE_DEBUG_INFO = NO; 467 | SDKROOT = iphoneos; 468 | TARGETED_DEVICE_FAMILY = "1,2"; 469 | VALIDATE_PRODUCT = YES; 470 | }; 471 | name = Release; 472 | }; 473 | C73B8DB71CFA6FEF007C73DA /* Debug */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 477 | CLANG_ENABLE_MODULES = YES; 478 | INFOPLIST_FILE = "MVCDemo/Supporting Files/Info.plist"; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 480 | PRODUCT_BUNDLE_IDENTIFIER = com.razeware.MVCDemo; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 483 | }; 484 | name = Debug; 485 | }; 486 | C73B8DB81CFA6FEF007C73DA /* Release */ = { 487 | isa = XCBuildConfiguration; 488 | buildSettings = { 489 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 490 | CLANG_ENABLE_MODULES = YES; 491 | INFOPLIST_FILE = "MVCDemo/Supporting Files/Info.plist"; 492 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 493 | PRODUCT_BUNDLE_IDENTIFIER = com.razeware.MVCDemo; 494 | PRODUCT_NAME = "$(TARGET_NAME)"; 495 | }; 496 | name = Release; 497 | }; 498 | C73B8DBA1CFA6FEF007C73DA /* Debug */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | BUNDLE_LOADER = "$(TEST_HOST)"; 502 | INFOPLIST_FILE = MVCDemoTests/Info.plist; 503 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 504 | PRODUCT_BUNDLE_IDENTIFIER = com.razeware.MVCDemoTests; 505 | PRODUCT_NAME = "$(TARGET_NAME)"; 506 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MVCDemo.app/MVCDemo"; 507 | }; 508 | name = Debug; 509 | }; 510 | C73B8DBB1CFA6FEF007C73DA /* Release */ = { 511 | isa = XCBuildConfiguration; 512 | buildSettings = { 513 | BUNDLE_LOADER = "$(TEST_HOST)"; 514 | INFOPLIST_FILE = MVCDemoTests/Info.plist; 515 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 516 | PRODUCT_BUNDLE_IDENTIFIER = com.razeware.MVCDemoTests; 517 | PRODUCT_NAME = "$(TARGET_NAME)"; 518 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MVCDemo.app/MVCDemo"; 519 | }; 520 | name = Release; 521 | }; 522 | /* End XCBuildConfiguration section */ 523 | 524 | /* Begin XCConfigurationList section */ 525 | C73B8D941CFA6FEF007C73DA /* Build configuration list for PBXProject "MVCDemo" */ = { 526 | isa = XCConfigurationList; 527 | buildConfigurations = ( 528 | C73B8DB41CFA6FEF007C73DA /* Debug */, 529 | C73B8DB51CFA6FEF007C73DA /* Release */, 530 | ); 531 | defaultConfigurationIsVisible = 0; 532 | defaultConfigurationName = Release; 533 | }; 534 | C73B8DB61CFA6FEF007C73DA /* Build configuration list for PBXNativeTarget "MVCDemo" */ = { 535 | isa = XCConfigurationList; 536 | buildConfigurations = ( 537 | C73B8DB71CFA6FEF007C73DA /* Debug */, 538 | C73B8DB81CFA6FEF007C73DA /* Release */, 539 | ); 540 | defaultConfigurationIsVisible = 0; 541 | defaultConfigurationName = Release; 542 | }; 543 | C73B8DB91CFA6FEF007C73DA /* Build configuration list for PBXNativeTarget "MVCDemoTests" */ = { 544 | isa = XCConfigurationList; 545 | buildConfigurations = ( 546 | C73B8DBA1CFA6FEF007C73DA /* Debug */, 547 | C73B8DBB1CFA6FEF007C73DA /* Release */, 548 | ); 549 | defaultConfigurationIsVisible = 0; 550 | defaultConfigurationName = Release; 551 | }; 552 | /* End XCConfigurationList section */ 553 | }; 554 | rootObject = C73B8D911CFA6FEF007C73DA /* Project object */; 555 | } 556 | -------------------------------------------------------------------------------- /MVCDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MVCDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | 28 | var window: UIWindow? = UIWindow(frame: UIScreen.mainScreen().bounds) 29 | 30 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 31 | 32 | let wwdcAttendeesViewController = WWDCAttendeesViewController(attendeesHandler: createController()) 33 | let navigationController = UINavigationController(rootViewController: wwdcAttendeesViewController) 34 | 35 | window?.rootViewController = navigationController 36 | window?.makeKeyAndVisible() 37 | 38 | return true 39 | } 40 | } 41 | 42 | private func createController() -> WWDCAttendeesController { 43 | 44 | //let mockedConnection = MockedConnection(fileName: "attendees") 45 | let connection = Connection(baseURL: NSURL(string: "https://dl.dropboxusercontent.com")!) 46 | let attendeesController = WWDCAttendeesController(connectable: connection) 47 | 48 | return attendeesController 49 | } 50 | 51 | -------------------------------------------------------------------------------- /MVCDemo/Components/Controllers/WWDCAttendeesController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | enum UIState { 26 | case Loading 27 | case Success([Attendee]) 28 | case Failure(Error) 29 | } 30 | 31 | protocol WWDCAttendesDelegate: class { 32 | var state: UIState { get set} 33 | } 34 | 35 | protocol WWDCAttendeesHandler: class { 36 | 37 | var delegate: WWDCAttendesDelegate? { get set } 38 | func fetchAttendees() 39 | } 40 | 41 | final class WWDCAttendeesController: WWDCAttendeesHandler { 42 | 43 | private let connectable: Connectable 44 | var delegate: WWDCAttendesDelegate? 45 | 46 | init(connectable: Connectable) { 47 | self.connectable = connectable 48 | } 49 | 50 | func fetchAttendees() { 51 | 52 | guard let delegate = self.delegate else { fatalError("did you forget to set the delegate?")} 53 | 54 | delegate.state = .Loading 55 | let resource = Resource(path: "/u/14102938/attendees.json", method: .GET) 56 | 57 | let parsingCompletion = createParsingCompletion(delegate) 58 | let networkCompletion = createNetworkCompletion(delegate, parseCompletion: parsingCompletion) 59 | 60 | connectable.makeConnection(resource, completion: networkCompletion) 61 | } 62 | } 63 | 64 | private typealias ParseCompletion = Result<[Attendee], Error> -> Void 65 | private typealias NetworkCompletion = Result -> Void 66 | 67 | private func createParsingCompletion(delegate: WWDCAttendesDelegate) -> ParseCompletion { 68 | 69 | return { atendeesResult in 70 | dispatch_async(dispatch_get_main_queue()) { 71 | switch atendeesResult { 72 | case .Success(let atendees): delegate.state = .Success(atendees) 73 | case .Failure(let error): delegate.state = .Failure(error) 74 | } 75 | } 76 | } 77 | } 78 | 79 | private func createNetworkCompletion(delegate: WWDCAttendesDelegate, parseCompletion: ParseCompletion) -> NetworkCompletion { 80 | 81 | return { dataResult in 82 | dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) { 83 | print(dataResult) 84 | switch dataResult { 85 | case .Success(let data): parse(data, completion: parseCompletion) 86 | case .Failure(let error): delegate.state = .Failure(error) 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /MVCDemo/Components/Error/Error.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | enum Error: ErrorType { 24 | case Network(String) 25 | case Parser 26 | } -------------------------------------------------------------------------------- /MVCDemo/Components/Models/Attendee.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | struct Attendee { 24 | let name: String 25 | let company: String 26 | } 27 | 28 | extension Attendee: Mappable { 29 | 30 | static func mapToModel(o: AnyObject) -> Result { 31 | 32 | guard 33 | let dic = o as? [String: AnyObject], 34 | let name = dic["name"] as? String, 35 | let company = dic["company"] as? String 36 | else { return .Failure(.Parser) } 37 | 38 | return .Success(Attendee(name: name, company: company)) 39 | } 40 | } -------------------------------------------------------------------------------- /MVCDemo/Components/Network/Connection.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | protocol Connectable { 26 | func makeConnection(resource: Resource, completion: Result -> Void) 27 | } 28 | 29 | final class Connection { 30 | private let session: NSURLSession 31 | private let baseURL: NSURL 32 | 33 | init(baseURL: NSURL, session: NSURLSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())) { 34 | self.baseURL = baseURL 35 | self.session = session 36 | } 37 | } 38 | 39 | extension Connection: Connectable { 40 | 41 | func makeConnection(resource: Resource, completion: Result -> Void) { 42 | 43 | let request = resource.toRequest(baseURL) 44 | 45 | session.dataTaskWithRequest(request) { (data, response, error) in 46 | switch (data, error) { 47 | case(let data?, _): completion(.Success(data)) 48 | case(_, let error?): completion(.Failure(.Network(error.description))) 49 | default: break 50 | } 51 | }.resume() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MVCDemo/Components/Network/MockedConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedConnection.swift 3 | // MVCDemo 4 | // 5 | // Created by Rui Peres on 30/05/2016. 6 | // Copyright © 2016 Razeware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class MockedConnection: Connectable { 12 | 13 | private let fileName: String 14 | 15 | init(fileName: String) { 16 | self.fileName = fileName 17 | } 18 | 19 | func makeConnection(resource: Resource, completion: Result -> Void) { 20 | 21 | let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "json")! 22 | let data = NSData(contentsOfFile: path)! 23 | 24 | completion(.Success(data)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MVCDemo/Components/Network/Resource.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public typealias Headers = [String: String] 26 | 27 | public struct Resource { 28 | 29 | public let path: String 30 | public let method: Method 31 | 32 | public var description: String { 33 | return "Path:\(path)\nMethod:\(method.rawValue)\n" 34 | } 35 | } 36 | 37 | public enum Method: String { 38 | case GET 39 | case POST 40 | } 41 | 42 | extension Resource { 43 | 44 | /// Used to transform a Resource into a NSURLRequest 45 | public func toRequest(baseURL: NSURL) -> NSURLRequest { 46 | 47 | let components = NSURLComponents(URL: baseURL, resolvingAgainstBaseURL: false) 48 | 49 | components?.path = path 50 | 51 | let finalURL = components?.URL ?? baseURL 52 | let request = NSMutableURLRequest(URL: finalURL) 53 | 54 | request.HTTPMethod = method.rawValue 55 | 56 | return request 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MVCDemo/Components/Parser/Parser.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | protocol Mappable { 26 | static func mapToModel(o: AnyObject) -> Result 27 | } 28 | 29 | func parse(data: NSData, completion: Result<[T], Error> -> Void) { 30 | 31 | let decodedData: Result = decodeData(data) 32 | 33 | switch decodedData { 34 | 35 | case .Success(let result): 36 | 37 | guard let array = result as? [AnyObject] else { completion(.Failure(.Parser)); return } 38 | 39 | let result: Result<[T], Error> = arrayToModels(array) 40 | completion(result) 41 | 42 | case .Failure: 43 | completion(.Failure(.Parser)) 44 | } 45 | } 46 | 47 | private func arrayToModels(objects: [AnyObject]) -> Result<[T], Error> { 48 | 49 | var convertAndCleanArray: [T] = [] 50 | 51 | for object in objects { 52 | 53 | guard case .Success(let model) = T.mapToModel(object) else { continue } 54 | convertAndCleanArray.append(model) 55 | } 56 | 57 | return .Success(convertAndCleanArray) 58 | } 59 | 60 | private func decodeData(data: NSData) -> Result { 61 | 62 | do { 63 | let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) 64 | return .Success(json) 65 | } 66 | catch { 67 | return .Failure(.Parser) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /MVCDemo/Components/Result/Result.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | enum Result { 24 | case Success(T) 25 | case Failure(E) 26 | } -------------------------------------------------------------------------------- /MVCDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /MVCDemo/Resources/Localizable.strings: -------------------------------------------------------------------------------- 1 | "wwdc_attendees" = "WWDC Attendees"; -------------------------------------------------------------------------------- /MVCDemo/Resources/attendees.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "Rui Peres", 3 | "company": "MailOnline" 4 | }, { 5 | "name": "Tiago Almeida", 6 | "company": "Thing Pink" 7 | }, { 8 | "name": "David Rodrigues", 9 | "company": "WIT-Software" 10 | }, { 11 | "name": "Renato Rodrigues", 12 | "company": "PixelMatters" 13 | }, { 14 | "name": "Hugo Ferreira", 15 | "company": "Equal Experts" 16 | } 17 | ] -------------------------------------------------------------------------------- /MVCDemo/Supporting Files/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 | 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 | -------------------------------------------------------------------------------- /MVCDemo/UI/Generic UI/CellConfigurable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | 24 | protocol CellConfigurable: class { 25 | 26 | associatedtype Controller 27 | var cellController: Controller? { get set } 28 | } 29 | -------------------------------------------------------------------------------- /MVCDemo/UI/Generic UI/TableView+Dequeuing.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | protocol Reusable: class { 26 | static var reuseIdentifier: String { get } 27 | } 28 | 29 | extension Reusable { 30 | static var reuseIdentifier: String { return String(Self) } 31 | } 32 | 33 | extension UITableView { 34 | 35 | func registerReusableCell(_: T.Type) { 36 | registerClass(T.self, forCellReuseIdentifier: T.reuseIdentifier) 37 | } 38 | 39 | func dequeueReusableCell(indexPath indexPath: NSIndexPath) -> T { 40 | return dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /MVCDemo/UI/Generic UI/TableViewDataSource.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | final class TableViewDataSource: NSObject, UITableViewDataSource { 26 | 27 | var dataSource: [Model] = [] { 28 | didSet { tableView.reloadData() } 29 | } 30 | 31 | private unowned var tableView: UITableView 32 | 33 | init(tableView: UITableView) { 34 | self.tableView = tableView 35 | 36 | tableView.registerReusableCell(Cell) 37 | } 38 | 39 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return dataSource.count 41 | } 42 | 43 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 44 | 45 | let cell: Cell = tableView.dequeueReusableCell(indexPath: indexPath) 46 | cell.cellController = dataSource[indexPath.row] 47 | return cell 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MVCDemo/UI/ViewControllers/AttendeeCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | extension AttendeeCell: Reusable {} 26 | extension AttendeeCell: CellConfigurable {} 27 | 28 | final class AttendeeCell: UITableViewCell { 29 | 30 | var cellController: AttendeeCellController? { 31 | didSet { 32 | 33 | guard let controller = cellController else { return } 34 | 35 | textLabel?.text = controller.name 36 | detailTextLabel?.text = controller.company 37 | } 38 | } 39 | 40 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 41 | super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier) 42 | } 43 | 44 | @available(*, unavailable) 45 | required init?(coder aDecoder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MVCDemo/UI/ViewControllers/AttendeeCellController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | protocol AttendeeCellRepresentable { 24 | 25 | var name: String { get } 26 | var company: String { get } 27 | } 28 | 29 | struct AttendeeCellController: AttendeeCellRepresentable { 30 | 31 | let name: String 32 | let company: String 33 | 34 | init(attendee: Attendee) { 35 | self.name = attendee.name 36 | self.company = attendee.company 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MVCDemo/UI/ViewControllers/WWDCAttendeesUIController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | final class WWDCAttendeesUIController { 26 | 27 | private unowned var view: UIView 28 | private let loadingView: LoadingView 29 | private let tableViewDataSource: TableViewDataSource 30 | 31 | var state: UIState = .Loading { 32 | willSet(newState) { 33 | update(newState) 34 | } 35 | } 36 | 37 | init(view: UIView, tableView: UITableView) { 38 | 39 | self.view = view 40 | self.loadingView = LoadingView(frame: CGRect(origin: .zero, size: tableView.frame.size)) 41 | self.tableViewDataSource = TableViewDataSource(tableView: tableView) 42 | 43 | tableView.dataSource = tableViewDataSource 44 | update(state) 45 | } 46 | } 47 | 48 | extension WWDCAttendeesUIController: WWDCAttendesDelegate { 49 | 50 | func update(newState: UIState) { 51 | 52 | switch(state, newState) { 53 | 54 | case (.Loading, .Loading): loadingToLoading() 55 | case (.Loading, .Success(let attendees)): loadingToSuccess(attendees) 56 | 57 | default: fatalError("Not yet implemented \(state) to \(newState)") 58 | } 59 | } 60 | 61 | func loadingToLoading() { 62 | view.addSubview(loadingView) 63 | loadingView.frame = CGRect(origin: .zero, size: view.frame.size) 64 | } 65 | 66 | func loadingToSuccess(attendees: [Attendee]) { 67 | loadingView.removeFromSuperview() 68 | tableViewDataSource.dataSource = attendees.map(AttendeeCellController.init) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /MVCDemo/UI/ViewControllers/WWDCAttendeesViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | final class WWDCAttendeesViewController: UIViewController { 26 | 27 | private let attendeesHandler: WWDCAttendeesHandler 28 | private var atteendeesUIController: WWDCAttendeesUIController! 29 | private let tableView = UITableView(frame: .zero) 30 | 31 | init(attendeesHandler: WWDCAttendeesHandler) { 32 | 33 | self.attendeesHandler = attendeesHandler 34 | super.init(nibName: nil, bundle: nil) 35 | } 36 | 37 | @available(*, unavailable) 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | override func viewDidLayoutSubviews() { 43 | super.viewDidLayoutSubviews() 44 | 45 | tableView.frame = view.frame 46 | } 47 | 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | 51 | view.addSubview(tableView) 52 | tableView.tableFooterView = UIView() 53 | 54 | title = NSLocalizedString("wwdc_attendees", comment: "") 55 | 56 | atteendeesUIController = WWDCAttendeesUIController(view: view, tableView: tableView) 57 | attendeesHandler.delegate = atteendeesUIController 58 | 59 | attendeesHandler.fetchAttendees() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /MVCDemo/UI/Views/LoadingView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | final class LoadingView: UIView { 26 | 27 | private let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray) 28 | 29 | override init(frame: CGRect) { 30 | 31 | spinner.startAnimating() 32 | 33 | super.init(frame: frame) 34 | 35 | addSubview(spinner) 36 | backgroundColor = UIColor.whiteColor() 37 | } 38 | 39 | @available(*, unavailable) 40 | required init?(coder aDecoder: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | override func layoutSubviews() { 45 | super.layoutSubviews() 46 | spinner.center = center 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MVCDemoTests/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 | -------------------------------------------------------------------------------- /MVCDemoTests/MVCDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MVCDemoTests.swift 3 | // MVCDemoTests 4 | // 5 | // Created by Rui Peres on 29/05/2016. 6 | // Copyright © 2016 Razeware. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MVCDemo 11 | 12 | class MVCDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Companion demo app for [Model-View-Controller (MVC) in iOS: A Modern Approach](https://www.raywenderlich.com/132662/mvc-in-ios-a-modern-approach) article 2 | --------------------------------------------------------------------------------