├── .gitignore ├── Demo Project ├── JSON TableView.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── JSON TableView │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Repository.swift │ └── ViewController.swift └── JSON TableViewTests │ ├── Info.plist │ └── JSON_TableViewTests.swift ├── JSON TableView.playground ├── contents.xcplayground ├── section-1.swift └── timeline.xctimeline ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ######################### 2 | # .gitignore file for Xcode4 and Xcode5 Source projects 3 | # 4 | # Apple bugs, waiting for Apple to fix/respond: 5 | # 6 | # 15564624 - what does the xccheckout file in Xcode5 do? Where's the documentation? 7 | # 8 | # Version 2.1 9 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects 10 | # 11 | # 2013 updates: 12 | # - fixed the broken "save personal Schemes" 13 | # - added line-by-line explanations for EVERYTHING (some were missing) 14 | # 15 | # NB: if you are storing "built" products, this WILL NOT WORK, 16 | # and you should use a different .gitignore (or none at all) 17 | # This file is for SOURCE projects, where there are many extra 18 | # files that we want to exclude 19 | # 20 | ######################### 21 | 22 | ##### 23 | # OS X temporary files that should never be committed 24 | # 25 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 26 | 27 | .DS_Store 28 | 29 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 30 | 31 | .Trashes 32 | 33 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 34 | 35 | *.swp 36 | 37 | # *.lock - this is used and abused by many editors for many different things. 38 | # For the main ones I use (e.g. Eclipse), it should be excluded 39 | # from source-control, but YMMV 40 | 41 | *.lock 42 | 43 | # 44 | # profile - REMOVED temporarily (on double-checking, this seems incorrect; I can't find it in OS X docs?) 45 | #profile 46 | 47 | 48 | #### 49 | # Xcode temporary files that should never be committed 50 | # 51 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this... 52 | 53 | *~.nib 54 | 55 | 56 | #### 57 | # Xcode build files - 58 | # 59 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" 60 | 61 | DerivedData/ 62 | 63 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" 64 | 65 | build/ 66 | 67 | 68 | ##### 69 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 70 | # 71 | # This is complicated: 72 | # 73 | # SOMETIMES you need to put this file in version control. 74 | # Apple designed it poorly - if you use "custom executables", they are 75 | # saved in this file. 76 | # 99% of projects do NOT use those, so they do NOT want to version control this file. 77 | # ..but if you're in the 1%, comment out the line "*.pbxuser" 78 | 79 | # .pbxuser: http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html 80 | 81 | *.pbxuser 82 | 83 | # .mode1v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 84 | 85 | *.mode1v3 86 | 87 | # .mode2v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 88 | 89 | *.mode2v3 90 | 91 | # .perspectivev3: http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file 92 | 93 | *.perspectivev3 94 | 95 | # NB: also, whitelist the default ones, some projects need to use these 96 | !default.pbxuser 97 | !default.mode1v3 98 | !default.mode2v3 99 | !default.perspectivev3 100 | 101 | 102 | #### 103 | # Xcode 4 - semi-personal settings 104 | # 105 | # 106 | # OPTION 1: --------------------------------- 107 | # throw away ALL personal settings (including custom schemes! 108 | # - unless they are "shared") 109 | # 110 | # NB: this is exclusive with OPTION 2 below 111 | xcuserdata 112 | 113 | # OPTION 2: --------------------------------- 114 | # get rid of ALL personal settings, but KEEP SOME OF THEM 115 | # - NB: you must manually uncomment the bits you want to keep 116 | # 117 | # NB: this *requires* git v1.8.2 or above; you may need to upgrade to latest OS X, 118 | # or manually install git over the top of the OS X version 119 | # NB: this is exclusive with OPTION 1 above 120 | # 121 | #xcuserdata/**/* 122 | 123 | # (requires option 2 above): Personal Schemes 124 | # 125 | #!xcuserdata/**/xcschemes/* 126 | 127 | #### 128 | # XCode 4 workspaces - more detailed 129 | # 130 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :) 131 | # 132 | # Workspace layout is quite spammy. For reference: 133 | # 134 | # /(root)/ 135 | # /(project-name).xcodeproj/ 136 | # project.pbxproj 137 | # /project.xcworkspace/ 138 | # contents.xcworkspacedata 139 | # /xcuserdata/ 140 | # /(your name)/xcuserdatad/ 141 | # UserInterfaceState.xcuserstate 142 | # /xcsshareddata/ 143 | # /xcschemes/ 144 | # (shared scheme name).xcscheme 145 | # /xcuserdata/ 146 | # /(your name)/xcuserdatad/ 147 | # (private scheme).xcscheme 148 | # xcschememanagement.plist 149 | # 150 | # 151 | 152 | #### 153 | # Xcode 4 - Deprecated classes 154 | # 155 | # Allegedly, if you manually "deprecate" your classes, they get moved here. 156 | # 157 | # We're using source-control, so this is a "feature" that we do not want! 158 | 159 | *.moved-aside 160 | 161 | #### 162 | # SVN: subversion locks which can mess with git 163 | *.svn 164 | *.svn/* 165 | 166 | #### 167 | # UNKNOWN: recommended by others, but I can't discover what these files are 168 | # 169 | # ...none. Everything is now explained. 170 | -------------------------------------------------------------------------------- /Demo Project/JSON TableView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EF4F653D1AA7FE9E00F85C70 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF4F653C1AA7FE9E00F85C70 /* Repository.swift */; }; 11 | EFC9C7361AA7BEB200070B3C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC9C7351AA7BEB200070B3C /* AppDelegate.swift */; }; 12 | EFC9C7381AA7BEB200070B3C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC9C7371AA7BEB200070B3C /* ViewController.swift */; }; 13 | EFC9C73B1AA7BEB200070B3C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EFC9C7391AA7BEB200070B3C /* Main.storyboard */; }; 14 | EFC9C73D1AA7BEB200070B3C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EFC9C73C1AA7BEB200070B3C /* Images.xcassets */; }; 15 | EFC9C7401AA7BEB200070B3C /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = EFC9C73E1AA7BEB200070B3C /* LaunchScreen.xib */; }; 16 | EFC9C74C1AA7BEB300070B3C /* JSON_TableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC9C74B1AA7BEB300070B3C /* JSON_TableViewTests.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | EFC9C7461AA7BEB300070B3C /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = EFC9C7281AA7BEB200070B3C /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = EFC9C72F1AA7BEB200070B3C; 25 | remoteInfo = "JSON TableView"; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | EF4F653C1AA7FE9E00F85C70 /* Repository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; 31 | EFC9C7301AA7BEB200070B3C /* JSON TableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JSON TableView.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | EFC9C7341AA7BEB200070B3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | EFC9C7351AA7BEB200070B3C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 34 | EFC9C7371AA7BEB200070B3C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 35 | EFC9C73A1AA7BEB200070B3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | EFC9C73C1AA7BEB200070B3C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 37 | EFC9C73F1AA7BEB200070B3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 38 | EFC9C7451AA7BEB300070B3C /* JSON TableViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "JSON TableViewTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | EFC9C74A1AA7BEB300070B3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | EFC9C74B1AA7BEB300070B3C /* JSON_TableViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON_TableViewTests.swift; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | EFC9C72D1AA7BEB200070B3C /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | EFC9C7421AA7BEB300070B3C /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | EFC9C7271AA7BEB200070B3C = { 62 | isa = PBXGroup; 63 | children = ( 64 | EFC9C7321AA7BEB200070B3C /* JSON TableView */, 65 | EFC9C7481AA7BEB300070B3C /* JSON TableViewTests */, 66 | EFC9C7311AA7BEB200070B3C /* Products */, 67 | ); 68 | sourceTree = ""; 69 | }; 70 | EFC9C7311AA7BEB200070B3C /* Products */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | EFC9C7301AA7BEB200070B3C /* JSON TableView.app */, 74 | EFC9C7451AA7BEB300070B3C /* JSON TableViewTests.xctest */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | EFC9C7321AA7BEB200070B3C /* JSON TableView */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | EFC9C7351AA7BEB200070B3C /* AppDelegate.swift */, 83 | EFC9C7371AA7BEB200070B3C /* ViewController.swift */, 84 | EFC9C7391AA7BEB200070B3C /* Main.storyboard */, 85 | EFC9C73C1AA7BEB200070B3C /* Images.xcassets */, 86 | EFC9C73E1AA7BEB200070B3C /* LaunchScreen.xib */, 87 | EFC9C7331AA7BEB200070B3C /* Supporting Files */, 88 | EF4F653C1AA7FE9E00F85C70 /* Repository.swift */, 89 | ); 90 | path = "JSON TableView"; 91 | sourceTree = ""; 92 | }; 93 | EFC9C7331AA7BEB200070B3C /* Supporting Files */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | EFC9C7341AA7BEB200070B3C /* Info.plist */, 97 | ); 98 | name = "Supporting Files"; 99 | sourceTree = ""; 100 | }; 101 | EFC9C7481AA7BEB300070B3C /* JSON TableViewTests */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | EFC9C74B1AA7BEB300070B3C /* JSON_TableViewTests.swift */, 105 | EFC9C7491AA7BEB300070B3C /* Supporting Files */, 106 | ); 107 | path = "JSON TableViewTests"; 108 | sourceTree = ""; 109 | }; 110 | EFC9C7491AA7BEB300070B3C /* Supporting Files */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | EFC9C74A1AA7BEB300070B3C /* Info.plist */, 114 | ); 115 | name = "Supporting Files"; 116 | sourceTree = ""; 117 | }; 118 | /* End PBXGroup section */ 119 | 120 | /* Begin PBXNativeTarget section */ 121 | EFC9C72F1AA7BEB200070B3C /* JSON TableView */ = { 122 | isa = PBXNativeTarget; 123 | buildConfigurationList = EFC9C74F1AA7BEB300070B3C /* Build configuration list for PBXNativeTarget "JSON TableView" */; 124 | buildPhases = ( 125 | EFC9C72C1AA7BEB200070B3C /* Sources */, 126 | EFC9C72D1AA7BEB200070B3C /* Frameworks */, 127 | EFC9C72E1AA7BEB200070B3C /* Resources */, 128 | ); 129 | buildRules = ( 130 | ); 131 | dependencies = ( 132 | ); 133 | name = "JSON TableView"; 134 | productName = "JSON TableView"; 135 | productReference = EFC9C7301AA7BEB200070B3C /* JSON TableView.app */; 136 | productType = "com.apple.product-type.application"; 137 | }; 138 | EFC9C7441AA7BEB300070B3C /* JSON TableViewTests */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = EFC9C7521AA7BEB300070B3C /* Build configuration list for PBXNativeTarget "JSON TableViewTests" */; 141 | buildPhases = ( 142 | EFC9C7411AA7BEB300070B3C /* Sources */, 143 | EFC9C7421AA7BEB300070B3C /* Frameworks */, 144 | EFC9C7431AA7BEB300070B3C /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | EFC9C7471AA7BEB300070B3C /* PBXTargetDependency */, 150 | ); 151 | name = "JSON TableViewTests"; 152 | productName = "JSON TableViewTests"; 153 | productReference = EFC9C7451AA7BEB300070B3C /* JSON TableViewTests.xctest */; 154 | productType = "com.apple.product-type.bundle.unit-test"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | EFC9C7281AA7BEB200070B3C /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastUpgradeCheck = 0630; 163 | ORGANIZATIONNAME = "Learn Swift"; 164 | TargetAttributes = { 165 | EFC9C72F1AA7BEB200070B3C = { 166 | CreatedOnToolsVersion = 6.3; 167 | }; 168 | EFC9C7441AA7BEB300070B3C = { 169 | CreatedOnToolsVersion = 6.3; 170 | TestTargetID = EFC9C72F1AA7BEB200070B3C; 171 | }; 172 | }; 173 | }; 174 | buildConfigurationList = EFC9C72B1AA7BEB200070B3C /* Build configuration list for PBXProject "JSON TableView" */; 175 | compatibilityVersion = "Xcode 3.2"; 176 | developmentRegion = English; 177 | hasScannedForEncodings = 0; 178 | knownRegions = ( 179 | en, 180 | Base, 181 | ); 182 | mainGroup = EFC9C7271AA7BEB200070B3C; 183 | productRefGroup = EFC9C7311AA7BEB200070B3C /* Products */; 184 | projectDirPath = ""; 185 | projectRoot = ""; 186 | targets = ( 187 | EFC9C72F1AA7BEB200070B3C /* JSON TableView */, 188 | EFC9C7441AA7BEB300070B3C /* JSON TableViewTests */, 189 | ); 190 | }; 191 | /* End PBXProject section */ 192 | 193 | /* Begin PBXResourcesBuildPhase section */ 194 | EFC9C72E1AA7BEB200070B3C /* Resources */ = { 195 | isa = PBXResourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | EFC9C73B1AA7BEB200070B3C /* Main.storyboard in Resources */, 199 | EFC9C7401AA7BEB200070B3C /* LaunchScreen.xib in Resources */, 200 | EFC9C73D1AA7BEB200070B3C /* Images.xcassets in Resources */, 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | EFC9C7431AA7BEB300070B3C /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXResourcesBuildPhase section */ 212 | 213 | /* Begin PBXSourcesBuildPhase section */ 214 | EFC9C72C1AA7BEB200070B3C /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | EFC9C7381AA7BEB200070B3C /* ViewController.swift in Sources */, 219 | EFC9C7361AA7BEB200070B3C /* AppDelegate.swift in Sources */, 220 | EF4F653D1AA7FE9E00F85C70 /* Repository.swift in Sources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | EFC9C7411AA7BEB300070B3C /* Sources */ = { 225 | isa = PBXSourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | EFC9C74C1AA7BEB300070B3C /* JSON_TableViewTests.swift in Sources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXSourcesBuildPhase section */ 233 | 234 | /* Begin PBXTargetDependency section */ 235 | EFC9C7471AA7BEB300070B3C /* PBXTargetDependency */ = { 236 | isa = PBXTargetDependency; 237 | target = EFC9C72F1AA7BEB200070B3C /* JSON TableView */; 238 | targetProxy = EFC9C7461AA7BEB300070B3C /* PBXContainerItemProxy */; 239 | }; 240 | /* End PBXTargetDependency section */ 241 | 242 | /* Begin PBXVariantGroup section */ 243 | EFC9C7391AA7BEB200070B3C /* Main.storyboard */ = { 244 | isa = PBXVariantGroup; 245 | children = ( 246 | EFC9C73A1AA7BEB200070B3C /* Base */, 247 | ); 248 | name = Main.storyboard; 249 | sourceTree = ""; 250 | }; 251 | EFC9C73E1AA7BEB200070B3C /* LaunchScreen.xib */ = { 252 | isa = PBXVariantGroup; 253 | children = ( 254 | EFC9C73F1AA7BEB200070B3C /* Base */, 255 | ); 256 | name = LaunchScreen.xib; 257 | sourceTree = ""; 258 | }; 259 | /* End PBXVariantGroup section */ 260 | 261 | /* Begin XCBuildConfiguration section */ 262 | EFC9C74D1AA7BEB300070B3C /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_UNREACHABLE_CODE = YES; 278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 279 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 280 | COPY_PHASE_STRIP = NO; 281 | DEBUG_INFORMATION_FORMAT = dwarf; 282 | ENABLE_STRICT_OBJC_MSGSEND = YES; 283 | GCC_C_LANGUAGE_STANDARD = gnu99; 284 | GCC_DYNAMIC_NO_PIC = NO; 285 | GCC_OPTIMIZATION_LEVEL = 0; 286 | GCC_PREPROCESSOR_DEFINITIONS = ( 287 | "DEBUG=1", 288 | "$(inherited)", 289 | ); 290 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 293 | GCC_WARN_UNDECLARED_SELECTOR = YES; 294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 295 | GCC_WARN_UNUSED_FUNCTION = YES; 296 | GCC_WARN_UNUSED_VARIABLE = YES; 297 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 298 | MTL_ENABLE_DEBUG_INFO = YES; 299 | ONLY_ACTIVE_ARCH = YES; 300 | SDKROOT = iphoneos; 301 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 302 | TARGETED_DEVICE_FAMILY = "1,2"; 303 | }; 304 | name = Debug; 305 | }; 306 | EFC9C74E1AA7BEB300070B3C /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BOOL_CONVERSION = YES; 315 | CLANG_WARN_CONSTANT_CONVERSION = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 321 | CLANG_WARN_UNREACHABLE_CODE = YES; 322 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 323 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 324 | COPY_PHASE_STRIP = NO; 325 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 326 | ENABLE_NS_ASSERTIONS = NO; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | GCC_C_LANGUAGE_STANDARD = gnu99; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | SDKROOT = iphoneos; 338 | TARGETED_DEVICE_FAMILY = "1,2"; 339 | VALIDATE_PRODUCT = YES; 340 | }; 341 | name = Release; 342 | }; 343 | EFC9C7501AA7BEB300070B3C /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 347 | INFOPLIST_FILE = "JSON TableView/Info.plist"; 348 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 349 | PRODUCT_NAME = "$(TARGET_NAME)"; 350 | }; 351 | name = Debug; 352 | }; 353 | EFC9C7511AA7BEB300070B3C /* Release */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | INFOPLIST_FILE = "JSON TableView/Info.plist"; 358 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 359 | PRODUCT_NAME = "$(TARGET_NAME)"; 360 | }; 361 | name = Release; 362 | }; 363 | EFC9C7531AA7BEB300070B3C /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | BUNDLE_LOADER = "$(TEST_HOST)"; 367 | FRAMEWORK_SEARCH_PATHS = ( 368 | "$(SDKROOT)/Developer/Library/Frameworks", 369 | "$(inherited)", 370 | ); 371 | GCC_PREPROCESSOR_DEFINITIONS = ( 372 | "DEBUG=1", 373 | "$(inherited)", 374 | ); 375 | INFOPLIST_FILE = "JSON TableViewTests/Info.plist"; 376 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JSON TableView.app/JSON TableView"; 379 | }; 380 | name = Debug; 381 | }; 382 | EFC9C7541AA7BEB300070B3C /* Release */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | BUNDLE_LOADER = "$(TEST_HOST)"; 386 | FRAMEWORK_SEARCH_PATHS = ( 387 | "$(SDKROOT)/Developer/Library/Frameworks", 388 | "$(inherited)", 389 | ); 390 | INFOPLIST_FILE = "JSON TableViewTests/Info.plist"; 391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 392 | PRODUCT_NAME = "$(TARGET_NAME)"; 393 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JSON TableView.app/JSON TableView"; 394 | }; 395 | name = Release; 396 | }; 397 | /* End XCBuildConfiguration section */ 398 | 399 | /* Begin XCConfigurationList section */ 400 | EFC9C72B1AA7BEB200070B3C /* Build configuration list for PBXProject "JSON TableView" */ = { 401 | isa = XCConfigurationList; 402 | buildConfigurations = ( 403 | EFC9C74D1AA7BEB300070B3C /* Debug */, 404 | EFC9C74E1AA7BEB300070B3C /* Release */, 405 | ); 406 | defaultConfigurationIsVisible = 0; 407 | defaultConfigurationName = Release; 408 | }; 409 | EFC9C74F1AA7BEB300070B3C /* Build configuration list for PBXNativeTarget "JSON TableView" */ = { 410 | isa = XCConfigurationList; 411 | buildConfigurations = ( 412 | EFC9C7501AA7BEB300070B3C /* Debug */, 413 | EFC9C7511AA7BEB300070B3C /* Release */, 414 | ); 415 | defaultConfigurationIsVisible = 0; 416 | defaultConfigurationName = Release; 417 | }; 418 | EFC9C7521AA7BEB300070B3C /* Build configuration list for PBXNativeTarget "JSON TableViewTests" */ = { 419 | isa = XCConfigurationList; 420 | buildConfigurations = ( 421 | EFC9C7531AA7BEB300070B3C /* Debug */, 422 | EFC9C7541AA7BEB300070B3C /* Release */, 423 | ); 424 | defaultConfigurationIsVisible = 0; 425 | defaultConfigurationName = Release; 426 | }; 427 | /* End XCConfigurationList section */ 428 | }; 429 | rootObject = EFC9C7281AA7BEB200070B3C /* Project object */; 430 | } 431 | -------------------------------------------------------------------------------- /Demo Project/JSON TableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo Project/JSON TableView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JSON TableView 4 | // 5 | // Created by John Clem on 3/4/15. 6 | // Copyright (c) 2015 Learn Swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Demo Project/JSON TableView/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Demo Project/JSON TableView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Demo Project/JSON TableView/Images.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 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Demo Project/JSON TableView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | io.learnSwift.$(PRODUCT_NAME:rfc1034identifier) 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 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Demo Project/JSON TableView/Repository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Repository.swift 3 | // JSON TableView 4 | // 5 | // Created by John Clem on 3/4/15. 6 | // Copyright (c) 2015 Learn Swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class Repository { 12 | 13 | var name: String? 14 | var description: String? 15 | var html_url: String? 16 | 17 | init(json: NSDictionary) { 18 | self.name = json["name"] as? String 19 | self.description = json["description"] as? String 20 | self.html_url = json["html_url"] as? String 21 | } 22 | } -------------------------------------------------------------------------------- /Demo Project/JSON TableView/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JSON TableView 4 | // 5 | // Created by John Clem on 3/4/15. 6 | // Copyright (c) 2015 Learn Swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, UITableViewDataSource { 12 | 13 | var repositories = [Repository]() 14 | 15 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 16 | return repositories.count 17 | } 18 | 19 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 20 | var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell 21 | cell.textLabel?.text = repositories[indexPath.row].name 22 | cell.detailTextLabel?.text = repositories[indexPath.row].description 23 | return cell 24 | } 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | // 1 29 | let reposURL = NSURL(string: "https://api.github.com/search/repositories?q=learn+swift+language:swift&sort=stars&order=desc") 30 | // 2 31 | if let JSONData = NSData(contentsOfURL: reposURL!) { 32 | // 3 33 | if let json = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: nil) as? NSDictionary { 34 | // 4 35 | if let reposArray = json["items"] as? [NSDictionary] { 36 | // 5 37 | for item in reposArray { 38 | repositories.append(Repository(json: item)) 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Demo Project/JSON TableViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | io.learnSwift.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /Demo Project/JSON TableViewTests/JSON_TableViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSON_TableViewTests.swift 3 | // JSON TableViewTests 4 | // 5 | // Created by John Clem on 3/4/15. 6 | // Copyright (c) 2015 Learn Swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class JSON_TableViewTests: 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 | XCTAssert(true, "Pass") 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 | -------------------------------------------------------------------------------- /JSON TableView.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /JSON TableView.playground/section-1.swift: -------------------------------------------------------------------------------- 1 | // Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | 5 | let screen = UIView(frame: CGRect(x: 0, y: 0, width: 480, height: 320)) 6 | screen.backgroundColor = UIColor.lightGrayColor() 7 | let tableView = UITableView(frame: screen.frame, style: .Grouped) 8 | 9 | class Repository { 10 | var name : String 11 | var description : String 12 | 13 | init(json: NSDictionary) { 14 | self.name = json["name"] as! String 15 | self.description = json["description"] as! String 16 | } 17 | } 18 | 19 | class TableViewDataSource : NSObject, UITableViewDataSource { 20 | 21 | var repositories = [Repository]() 22 | 23 | init(repositories: [Repository]) { 24 | self.repositories = repositories 25 | super.init() 26 | } 27 | 28 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 29 | return repositories.count 30 | } 31 | 32 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 33 | let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: nil) 34 | cell.textLabel?.text = repositories[indexPath.row].name 35 | cell.detailTextLabel?.text = repositories[indexPath.row].description 36 | return cell 37 | } 38 | } 39 | 40 | let sampleRepoOne = ["name": "learn-swift", "html_url": "https://github.com/nettlep/learn-swift", "description": "Learn Apple's Swift programming language interactively through these playgrounds."] 41 | let sampleRepoTwo = ["name": "swift-reference-pg", "html_url": "https://github.com/hackswift", "description": "Swift Reference is a handy playground file that can be used when you are starting to learn swift"] 42 | 43 | let dataSource = TableViewDataSource(repositories: [Repository(json: sampleRepoOne), Repository(json: sampleRepoTwo)]) 44 | 45 | tableView.dataSource = dataSource 46 | screen.addSubview(tableView) 47 | tableView.reloadData() 48 | 49 | screen 50 | 51 | -------------------------------------------------------------------------------- /JSON TableView.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 John Clem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## JSON-backed UITableView in Swift 2 | 3 | Recently, I wrote about parsing [JSON][1] and again revisited some updated syntax for [JSON][1] parsing in [Swift 1.2][2]. I received an email from learnSwift reader Shameer asking for a practical example of using JSON in an iOS app, so this post will focus on the most practical of all iOS examples: UITableView. 4 | 5 | I've implemented UITableViews in nearly every app I've ever written, including countless class demos. It's the perfect topic for illustrating some of the most common, and often misunderstood, design patterns in Cocoa; such as Delegation, Model-View-Controller and Target-Action. It's also a great test-bed for learning about more advanced topics such as lazy-loading, memory management & multi-threaded programming. 6 | 7 | UITableView is a cornerstone of UIKit; allowing you to easily display a scrollable, tap-able list of dynamically generated items with buttery smooth performance. If you're not familiar with UITableView, open the Settings app on your phone (or just about any other app that displays data) and you'll see examples of UITableView. Unlike it's sexier, more custommizable counterpart, UICollectionView, UITableView has been with us on iOS since the very first iPhone, and very little of the API for UITableView has changed since then, but that's a good thing. 8 | 9 | Creating a UITableView is quite simple, just create it using one of the available initializers, and add it to your view heirarchy: 10 | 11 | ``` 12 | let tableView = UITableView(frame: self.view.frame, style: .Grouped) 13 | self.view.addSubview(tableView) 14 | ``` 15 | 16 | More likely than not, however, if you're not a tableView expert, you might prefer to create the tableView through interface builder. Just drag a UITableView object onto your storyboard. 17 | 18 | With the easy part out of the way, now we must discuss how a UITableView actually works. You could pre-populate your table with static data, which is helpful for things like Settings / Preferences, but more often than not, you're creating a table to display data that doesn't exist yet (i.e. data you'll download from the web at runtime). To populate a tableView with dynamic data, however, the tableView needs three things: 19 | 20 | 1. Table Cell prototype(s) for each type of cell the table will display 21 | 2. How many cells/rows to draw 22 | 3. How to configure a cell prototype for a given row 23 | 24 | **Table Cell Prototypes** 25 | 26 | Table cell prototypes allow you to design a TableViewCell in Interface Builder (or programmatically) which will be re-used for each row in your tableView. TableViews utilizing dynamic data must have at least one dynamic prototype, but can have multiple prototype cells if necessary. Typically you will create one dynamic prototype for each distinct type of cell your table will display (e.g. a simple text-only prototype cell, and a second prototype cell with text and a large photo). 27 | 28 | The most straight-forward way to create dynamic prototype cells is through interface builder. 29 | 30 | Since TableViews can support multiple dynamic prototypes, each prototype cell needs its own unique identifier. The identifier is a case-sensitive string value, similar to a segue identifier. For this example, I'm using "Cell" as the identifier. 31 | 32 | **How Many Rows -- tableView:numberOfRowsInSection:** 33 | 34 | The next step is letting our tableView know how many cells to create. Since our data is dynamic, this is done at runtime and typically corresponds to the count of an array of objects in our viewController. Before we can implement the necessary method, we need to set our ViewController as the dataSource for the tableView. 35 | 36 | Now that the interface is setup, the tableView will ask its dataSource for numberOfRowsInSection and for each row, it will then ask the dataSource for a cell using the tableView:cellForRowAtIndexPath: method. We've told the tableView that ViewController will be its dataSource. The dataSource for a tableView is any object that conforms to the UITableViewDataSource Protocol. 37 | 38 | If you're not familiar with Protocols, think of it as a contract between two objects in your code. The ViewController is going to conform to the dataSource protocol, this means that the ViewController "promises" to respond to the required methods in the protocol (numberOfRows... and cellForRow...) and may or may not respond to the optional methods in the protocol. To "opt-in" to the UITableViewDataSource protocol, simply add it to your class declaration like so: 39 | 40 | ``` 41 | class ViewController: UIViewController, UITableViewDataSource... 42 | ``` 43 | 44 | If you build and run the app at this point, it will crash immediately, informing you that ViewController does not respond to the selector tableView:numberOfRowsInSection: All this means is that the tableView loaded, looked for its dataSource, found it (ViewController) then called the dataSource method(s) on it. Let's fix this crash by fullfilling ViewController's end of the contract and implementing the required methods. 45 | 46 | First, tableView:numberOfRowsInSection: 47 | 48 | ``` 49 | var repositories = [String]() 50 | 51 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return repositories.count 53 | } 54 | ``` 55 | 56 | The first line simply creates an array to hold our repositories. Later on, we'll be downloading them as JSON from the GitHub API and de-serializing them into their own model objects, but for now, we'll just use an array of strings to keep things simple. 57 | 58 | Next, we implement the tableView:numberOfRowsInSection: method by returning an Int. Since we don't know how many rows we'll actually need until runtime, we return repositories.count, meaning we always want the number of rows in the table to match the number of repositories in our array (it feels like cheating, I know, but it's just that simple). 59 | 60 | Next, the other required method in the dataSource protocol: tableView:cellForRowAtIndexPath: 61 | 62 | 63 | ``` 64 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 65 | var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell 66 | cell.textLabel?.text = repositories[indexPath.row] 67 | return cell 68 | } 69 | ``` 70 | 71 | Three lines is about as short as this method can be. I've seen implementations of this method that span multiple pages, but keep in mine, this method will run once for each cell, everytime it needs to be drawn on screen. 72 | 73 | Looking at the code for this method, the first line asks the tableView for a reusable cell for the given identifier. Next, we assign a string to the textLabel?.text. Note that textLabel is optional, this is because not all UITableViewCells have a textLabel, so using optional assignment lets us assign the string, but only if the label exists. Finally, we return the cell to the caller (which in this case will be the tableView). 74 | 75 | At this point, your ViewController class should look something like this: 76 | 77 | ``` 78 | import UIKit 79 | 80 | class ViewController: UIViewController, UITableViewDataSource { 81 | 82 | var repositories = [String]() 83 | 84 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 85 | return repositories.count 86 | } 87 | 88 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 89 | var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell 90 | cell.textLabel?.text = repositories[indexPath.row] 91 | return cell 92 | } 93 | } 94 | ``` 95 | 96 | This leads to perhaps the most interesting discussion around UITableView: _What the hell does dequeueReusableCellWithIdentifier mean?_ Remember what I said about UITableView being highly optimized for performance, and how it's been around since the very first versions of iOS? This notion of dequeueing reusable cells is what gives UITableView its unfair advantage when it comes to performance. 97 | 98 | Suppose you fetch some data from the web and then display it in your tableView. If your network request comes back with 3 or 4 results, you could create tableView cells for each result without any issues. But let's suppose your network request comes back with 10,000 results. As you scroll your tableView, your app's memory footprint would start to grow. Before long, the interface would start to lag, and long before you scrolled to the bottom of your tableView, the app would slow to a crawl, or crash. Queueing and de-queueing cells tackles this problem in a rather elegant way; taking advantage of the fact that it is much more performant and much less resource intensive to swap out the data of a tableView cell and reuse it, than to create each cell from scratch. 99 | 100 | You'll also recall that we gave our dynamic prototype cell a _Reuse Identifier._ Here's what happens when you ask a tableView to dequeue a reusable cell for a given identifier: 101 | 102 | 1. The tableView searches for a prototype cell with a matching identifier (if a prototype isn't found for the identifier, it's an instant crash) 103 | 1. If the tableView doesn't have a cell for the given identifier available for re-use, it creates a new cell from the prototype 104 | 1. If the tableView does have a cell for the given identifier in its re-use pool (more on this in a moment) the cell is "de-queued" or pulled out of the re-use pool and returned to the called (ViewController in our case) 105 | 106 | The tableView's re-use pool is an array of tableView cells that have already been constructed. Meaning, we've already paid the computational cost for creating the UI elements for the cell, but the cell is no longer in use. The question you may be asking yourself at this point is, "How does a cell end up in the reuse pool?" This happens when the cell is pushed off-screen. 107 | 108 | For example: 109 | 110 | * you have 10 items in your tableView 111 | * only 5 can fit on-screen at once 112 | * when the table first loads, cells 0-4 are created from the prototype 113 | * when the table scrolls, the re-use pool is empty, so cell #5 is created from the prototype, while cell #0 scrolls off-screen and is added to the re-use pool 114 | * when cell #6 is ready to scroll on-screen, the tableView has a cell available for re-use, so it de-queues cell #0 for re-use, while enqueuing cell #1 for re-use and so-on 115 | 116 | Ok, enough nitty-gritty UITableView shop talk, let's move on to deserializing JSON into model objects and turning out String-backed-table into a Model-backed-table. 117 | 118 | First thing we'll need is a new Swift file to contain our Repository model class. We could actually declare it as a nested class in ViewController.swift, but it's cleaner and more maintainable (IMHO) to give it a separate file. 119 | 120 | To create a new Swift file, choose 121 | **File -> New -> File** and choose **Swift File** as the file type (not **Cocoa Touch Class**). Name the file **Repository** and click *Create*. According to the Github API docs, there are dozens of potential properties a Repository could have, but we only need to write in the properties we actually care about. For this tutorial, **name**, **description** and **html_url** should be all we need. 122 | 123 | Edit Repository.swift to look like this: 124 | 125 | ``` 126 | import UIKit 127 | 128 | class Repository { 129 | 130 | var name: String? 131 | var description: String? 132 | var html_url: String? 133 | 134 | init(json: NSDictionary) { 135 | self.name = json["name"] as? String 136 | self.description = json["description"] as? String 137 | self.html_url = json["html_url"] as? String 138 | } 139 | } 140 | ``` 141 | 142 | Nothing out of the ordinary going on here. We declare the three properties that the class needs as **var**s of the **String** type. The only other thing going on in this model class is a designated initializer which will take an NSDictionary, extract the relevant values and assign them to its properties. 143 | 144 | With our Model class complete, let's head back over to ViewController.swift and update it to use Repositories instead of Strings. Update ViewController.swift like so: 145 | 146 | ``` 147 | import UIKit 148 | 149 | class ViewController: UIViewController, UITableViewDataSource { 150 | 151 | var repositories = [Repository]() 152 | 153 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 154 | return repositories.count 155 | } 156 | 157 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 158 | var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell 159 | cell.textLabel?.text = repositories[indexPath.row].name 160 | cell.detailTextLabel?.text = repositories[indexPath.row].description 161 | return cell 162 | } 163 | } 164 | ``` 165 | 166 | Only a few changes were necessary; we change the type of the repositories array from **[String]** to **[Repository]**, then just update tableView:cellForRowAtIndexPath: to use the .name property of the array item, rather than the item itself. I've also added an additional line here to set the repository description as the detailText. 167 | 168 | Last, but not least, we write the code to make the network call, construct the repository objects, and reload the table. The logical place to put this for our demo app is when the view first loads, so add an override method for viewDidLoad like this: 169 | 170 | ``` 171 | override func viewDidLoad() { 172 | super.viewDidLoad() 173 | // 1 174 | let reposURL = NSURL(string: "https://api.github.com/search/repositories?q=learn+swift+language:swift&sort=stars&order=desc") 175 | // 2 176 | if let JSONData = NSData(contentsOfURL: reposURL!) { 177 | // 3 178 | if let json = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: nil) as? NSDictionary { 179 | // 4 180 | if let reposArray = json["items"] as? [NSDictionary] { 181 | // 5 182 | for item in reposArray { 183 | repositories.append(Repository(json: item)) 184 | } 185 | } 186 | } 187 | } 188 | } 189 | ``` 190 | 191 | Build and run, and you should see a fully populated tableView full of Github repos from the search string "learn swift." Let's take this method line-by-line to get a sense for what's going on. 192 | 193 | 1. First, we make the call to super.viewDidLoad() to call the superclass implementation (note: if you forget to call super.viewDidLoad(), bad/unexpected things can happen). We then create an NSURL object with a search string endpoint for the Github API. Feel free to paste that URL into your browser to see the raw response your app will be deserializing. 194 | 195 | 2. Next, we optionally unwrap an NSData object with the contents of the URL. This could fail for a variety of reasons (API rate limiting, no network connection, etc.) so an **if/let** is appropriate. 196 | 197 | 3. Now comes the magic; we optionally unwrap the NSData into an NSDictionary using the NSJSONSerialization class. 198 | 199 | 4. Now that we have an NSDictionary representation of the JSON response, we can look at the raw response in the browser to see that the actual repository data is nested under the "items" key, so we optionally unwrap the items into an Array of NSDictionaries 200 | 201 | 5. Lastly, we iterate over the array of items, constructing a new Repository object for each one, and adding it to the repositories array. 202 | 203 | **Next Steps:** 204 | 205 | If you followed along in Xcode, you'll notice that we have an html_url property on our model class that was never used. Try adding a segue to a UIWebView and load the web page for the repo. Beyond that, add a search bar to the table, and you pretty much have a full featured Github app. 206 | 207 | If you didn't follow along, be sure to grab the [code for the completed example project on Github here][1]. Also included in the repo is a simple Playground showing a minimal UITableView dataSource implementation. 208 | 209 | Either way, if you like this post, found it useful, or have a better / alternative way of doing it, please leave a comment below, and thanks for making it to the end of a rather long, and hopefully informative post. 210 | [1]: https://github.com/johnnyclem/JSON-TableView-in-Swift 211 | [2]: /blog?tag=Swift%201.2 212 | [1]: /blog?tag=json --------------------------------------------------------------------------------