├── .gitignore ├── MusicPlayer.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── MusicPlayer ├── APIController.swift ├── Album.swift ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Blank52.png ├── DetailsViewController.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── SearchResultsViewController.swift ├── Track.swift └── TrackCell.swift └── MusicPlayerTests ├── Info.plist └── MusicPlayerTests.swift /.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 | # Xcode 5 - VCS file 163 | # The data in this file not represent state of your project. 164 | # If you'll leave this file in git - you will have merge conflicts during 165 | # pull your cahnges to other's repo 166 | # 167 | *.xccheckout 168 | 169 | #### 170 | # UNKNOWN: recommended by others, but I can't discover what these files are 171 | # 172 | # ...none. Everything is now explained. -------------------------------------------------------------------------------- /MusicPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0270480919C8E08B00FDA1C5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270480819C8E08B00FDA1C5 /* AppDelegate.swift */; }; 11 | 0270480B19C8E08B00FDA1C5 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270480A19C8E08B00FDA1C5 /* SearchResultsViewController.swift */; }; 12 | 0270480E19C8E08B00FDA1C5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0270480C19C8E08B00FDA1C5 /* Main.storyboard */; }; 13 | 0270481019C8E08B00FDA1C5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0270480F19C8E08B00FDA1C5 /* Images.xcassets */; }; 14 | 0270481319C8E08B00FDA1C5 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0270481119C8E08B00FDA1C5 /* LaunchScreen.xib */; }; 15 | 0270481F19C8E08B00FDA1C5 /* MusicPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270481E19C8E08B00FDA1C5 /* MusicPlayerTests.swift */; }; 16 | 0270482919C8E2E700FDA1C5 /* APIController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270482819C8E2E700FDA1C5 /* APIController.swift */; }; 17 | 0270482B19C8EC3000FDA1C5 /* Blank52.png in Resources */ = {isa = PBXBuildFile; fileRef = 0270482A19C8EC3000FDA1C5 /* Blank52.png */; }; 18 | 0270482D19C8F35700FDA1C5 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270482C19C8F35700FDA1C5 /* Album.swift */; }; 19 | 0270482F19C8F43500FDA1C5 /* DetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270482E19C8F43500FDA1C5 /* DetailsViewController.swift */; }; 20 | 0270483119C8F6D000FDA1C5 /* Track.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270483019C8F6D000FDA1C5 /* Track.swift */; }; 21 | 0270483519C8F80000FDA1C5 /* TrackCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270483419C8F80000FDA1C5 /* TrackCell.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 0270481919C8E08B00FDA1C5 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 027047FB19C8E08B00FDA1C5 /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 0270480219C8E08B00FDA1C5; 30 | remoteInfo = MusicPlayer; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 0270480319C8E08B00FDA1C5 /* MusicPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MusicPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 0270480719C8E08B00FDA1C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 0270480819C8E08B00FDA1C5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | 0270480A19C8E08B00FDA1C5 /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = ""; }; 39 | 0270480D19C8E08B00FDA1C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 40 | 0270480F19C8E08B00FDA1C5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 41 | 0270481219C8E08B00FDA1C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 42 | 0270481819C8E08B00FDA1C5 /* MusicPlayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MusicPlayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 0270481D19C8E08B00FDA1C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 0270481E19C8E08B00FDA1C5 /* MusicPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicPlayerTests.swift; sourceTree = ""; }; 45 | 0270482819C8E2E700FDA1C5 /* APIController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIController.swift; sourceTree = ""; }; 46 | 0270482A19C8EC3000FDA1C5 /* Blank52.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Blank52.png; sourceTree = ""; }; 47 | 0270482C19C8F35700FDA1C5 /* Album.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = ""; }; 48 | 0270482E19C8F43500FDA1C5 /* DetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsViewController.swift; sourceTree = ""; }; 49 | 0270483019C8F6D000FDA1C5 /* Track.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Track.swift; sourceTree = ""; }; 50 | 0270483419C8F80000FDA1C5 /* TrackCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackCell.swift; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 0270480019C8E08B00FDA1C5 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | 0270481519C8E08B00FDA1C5 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 027047FA19C8E08B00FDA1C5 = { 72 | isa = PBXGroup; 73 | children = ( 74 | 0270480519C8E08B00FDA1C5 /* MusicPlayer */, 75 | 0270481B19C8E08B00FDA1C5 /* MusicPlayerTests */, 76 | 0270480419C8E08B00FDA1C5 /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | 0270480419C8E08B00FDA1C5 /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 0270480319C8E08B00FDA1C5 /* MusicPlayer.app */, 84 | 0270481819C8E08B00FDA1C5 /* MusicPlayerTests.xctest */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 0270480519C8E08B00FDA1C5 /* MusicPlayer */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 0270482A19C8EC3000FDA1C5 /* Blank52.png */, 93 | 0270480819C8E08B00FDA1C5 /* AppDelegate.swift */, 94 | 0270480A19C8E08B00FDA1C5 /* SearchResultsViewController.swift */, 95 | 0270480C19C8E08B00FDA1C5 /* Main.storyboard */, 96 | 0270480F19C8E08B00FDA1C5 /* Images.xcassets */, 97 | 0270481119C8E08B00FDA1C5 /* LaunchScreen.xib */, 98 | 0270480619C8E08B00FDA1C5 /* Supporting Files */, 99 | 0270482819C8E2E700FDA1C5 /* APIController.swift */, 100 | 0270482C19C8F35700FDA1C5 /* Album.swift */, 101 | 0270482E19C8F43500FDA1C5 /* DetailsViewController.swift */, 102 | 0270483019C8F6D000FDA1C5 /* Track.swift */, 103 | 0270483419C8F80000FDA1C5 /* TrackCell.swift */, 104 | ); 105 | path = MusicPlayer; 106 | sourceTree = ""; 107 | }; 108 | 0270480619C8E08B00FDA1C5 /* Supporting Files */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 0270480719C8E08B00FDA1C5 /* Info.plist */, 112 | ); 113 | name = "Supporting Files"; 114 | sourceTree = ""; 115 | }; 116 | 0270481B19C8E08B00FDA1C5 /* MusicPlayerTests */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 0270481E19C8E08B00FDA1C5 /* MusicPlayerTests.swift */, 120 | 0270481C19C8E08B00FDA1C5 /* Supporting Files */, 121 | ); 122 | path = MusicPlayerTests; 123 | sourceTree = ""; 124 | }; 125 | 0270481C19C8E08B00FDA1C5 /* Supporting Files */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 0270481D19C8E08B00FDA1C5 /* Info.plist */, 129 | ); 130 | name = "Supporting Files"; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXNativeTarget section */ 136 | 0270480219C8E08B00FDA1C5 /* MusicPlayer */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = 0270482219C8E08B00FDA1C5 /* Build configuration list for PBXNativeTarget "MusicPlayer" */; 139 | buildPhases = ( 140 | 027047FF19C8E08B00FDA1C5 /* Sources */, 141 | 0270480019C8E08B00FDA1C5 /* Frameworks */, 142 | 0270480119C8E08B00FDA1C5 /* Resources */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = MusicPlayer; 149 | productName = MusicPlayer; 150 | productReference = 0270480319C8E08B00FDA1C5 /* MusicPlayer.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | 0270481719C8E08B00FDA1C5 /* MusicPlayerTests */ = { 154 | isa = PBXNativeTarget; 155 | buildConfigurationList = 0270482519C8E08B00FDA1C5 /* Build configuration list for PBXNativeTarget "MusicPlayerTests" */; 156 | buildPhases = ( 157 | 0270481419C8E08B00FDA1C5 /* Sources */, 158 | 0270481519C8E08B00FDA1C5 /* Frameworks */, 159 | 0270481619C8E08B00FDA1C5 /* Resources */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | 0270481A19C8E08B00FDA1C5 /* PBXTargetDependency */, 165 | ); 166 | name = MusicPlayerTests; 167 | productName = MusicPlayerTests; 168 | productReference = 0270481819C8E08B00FDA1C5 /* MusicPlayerTests.xctest */; 169 | productType = "com.apple.product-type.bundle.unit-test"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | 027047FB19C8E08B00FDA1C5 /* Project object */ = { 175 | isa = PBXProject; 176 | attributes = { 177 | LastUpgradeCheck = 0600; 178 | ORGANIZATIONNAME = "JQ Software LLC"; 179 | TargetAttributes = { 180 | 0270480219C8E08B00FDA1C5 = { 181 | CreatedOnToolsVersion = 6.0; 182 | }; 183 | 0270481719C8E08B00FDA1C5 = { 184 | CreatedOnToolsVersion = 6.0; 185 | TestTargetID = 0270480219C8E08B00FDA1C5; 186 | }; 187 | }; 188 | }; 189 | buildConfigurationList = 027047FE19C8E08B00FDA1C5 /* Build configuration list for PBXProject "MusicPlayer" */; 190 | compatibilityVersion = "Xcode 3.2"; 191 | developmentRegion = English; 192 | hasScannedForEncodings = 0; 193 | knownRegions = ( 194 | en, 195 | Base, 196 | ); 197 | mainGroup = 027047FA19C8E08B00FDA1C5; 198 | productRefGroup = 0270480419C8E08B00FDA1C5 /* Products */; 199 | projectDirPath = ""; 200 | projectRoot = ""; 201 | targets = ( 202 | 0270480219C8E08B00FDA1C5 /* MusicPlayer */, 203 | 0270481719C8E08B00FDA1C5 /* MusicPlayerTests */, 204 | ); 205 | }; 206 | /* End PBXProject section */ 207 | 208 | /* Begin PBXResourcesBuildPhase section */ 209 | 0270480119C8E08B00FDA1C5 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 0270480E19C8E08B00FDA1C5 /* Main.storyboard in Resources */, 214 | 0270482B19C8EC3000FDA1C5 /* Blank52.png in Resources */, 215 | 0270481319C8E08B00FDA1C5 /* LaunchScreen.xib in Resources */, 216 | 0270481019C8E08B00FDA1C5 /* Images.xcassets in Resources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | 0270481619C8E08B00FDA1C5 /* Resources */ = { 221 | isa = PBXResourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXResourcesBuildPhase section */ 228 | 229 | /* Begin PBXSourcesBuildPhase section */ 230 | 027047FF19C8E08B00FDA1C5 /* Sources */ = { 231 | isa = PBXSourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | 0270483519C8F80000FDA1C5 /* TrackCell.swift in Sources */, 235 | 0270480B19C8E08B00FDA1C5 /* SearchResultsViewController.swift in Sources */, 236 | 0270480919C8E08B00FDA1C5 /* AppDelegate.swift in Sources */, 237 | 0270482919C8E2E700FDA1C5 /* APIController.swift in Sources */, 238 | 0270482D19C8F35700FDA1C5 /* Album.swift in Sources */, 239 | 0270482F19C8F43500FDA1C5 /* DetailsViewController.swift in Sources */, 240 | 0270483119C8F6D000FDA1C5 /* Track.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | 0270481419C8E08B00FDA1C5 /* Sources */ = { 245 | isa = PBXSourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | 0270481F19C8E08B00FDA1C5 /* MusicPlayerTests.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXTargetDependency section */ 255 | 0270481A19C8E08B00FDA1C5 /* PBXTargetDependency */ = { 256 | isa = PBXTargetDependency; 257 | target = 0270480219C8E08B00FDA1C5 /* MusicPlayer */; 258 | targetProxy = 0270481919C8E08B00FDA1C5 /* PBXContainerItemProxy */; 259 | }; 260 | /* End PBXTargetDependency section */ 261 | 262 | /* Begin PBXVariantGroup section */ 263 | 0270480C19C8E08B00FDA1C5 /* Main.storyboard */ = { 264 | isa = PBXVariantGroup; 265 | children = ( 266 | 0270480D19C8E08B00FDA1C5 /* Base */, 267 | ); 268 | name = Main.storyboard; 269 | sourceTree = ""; 270 | }; 271 | 0270481119C8E08B00FDA1C5 /* LaunchScreen.xib */ = { 272 | isa = PBXVariantGroup; 273 | children = ( 274 | 0270481219C8E08B00FDA1C5 /* Base */, 275 | ); 276 | name = LaunchScreen.xib; 277 | sourceTree = ""; 278 | }; 279 | /* End PBXVariantGroup section */ 280 | 281 | /* Begin XCBuildConfiguration section */ 282 | 0270482019C8E08B00FDA1C5 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 287 | CLANG_CXX_LIBRARY = "libc++"; 288 | CLANG_ENABLE_MODULES = YES; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INT_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_UNREACHABLE_CODE = YES; 298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 299 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 300 | COPY_PHASE_STRIP = NO; 301 | ENABLE_STRICT_OBJC_MSGSEND = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_OPTIMIZATION_LEVEL = 0; 305 | GCC_PREPROCESSOR_DEFINITIONS = ( 306 | "DEBUG=1", 307 | "$(inherited)", 308 | ); 309 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 317 | MTL_ENABLE_DEBUG_INFO = YES; 318 | ONLY_ACTIVE_ARCH = YES; 319 | SDKROOT = iphoneos; 320 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 321 | }; 322 | name = Debug; 323 | }; 324 | 0270482119C8E08B00FDA1C5 /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 329 | CLANG_CXX_LIBRARY = "libc++"; 330 | CLANG_ENABLE_MODULES = YES; 331 | CLANG_ENABLE_OBJC_ARC = YES; 332 | CLANG_WARN_BOOL_CONVERSION = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 342 | COPY_PHASE_STRIP = YES; 343 | ENABLE_NS_ASSERTIONS = NO; 344 | ENABLE_STRICT_OBJC_MSGSEND = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu99; 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 353 | MTL_ENABLE_DEBUG_INFO = NO; 354 | SDKROOT = iphoneos; 355 | VALIDATE_PRODUCT = YES; 356 | }; 357 | name = Release; 358 | }; 359 | 0270482319C8E08B00FDA1C5 /* Debug */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | INFOPLIST_FILE = MusicPlayer/Info.plist; 364 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 365 | PRODUCT_NAME = "$(TARGET_NAME)"; 366 | }; 367 | name = Debug; 368 | }; 369 | 0270482419C8E08B00FDA1C5 /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 373 | INFOPLIST_FILE = MusicPlayer/Info.plist; 374 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 375 | PRODUCT_NAME = "$(TARGET_NAME)"; 376 | }; 377 | name = Release; 378 | }; 379 | 0270482619C8E08B00FDA1C5 /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | BUNDLE_LOADER = "$(TEST_HOST)"; 383 | FRAMEWORK_SEARCH_PATHS = ( 384 | "$(SDKROOT)/Developer/Library/Frameworks", 385 | "$(inherited)", 386 | ); 387 | GCC_PREPROCESSOR_DEFINITIONS = ( 388 | "DEBUG=1", 389 | "$(inherited)", 390 | ); 391 | INFOPLIST_FILE = MusicPlayerTests/Info.plist; 392 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 393 | PRODUCT_NAME = "$(TARGET_NAME)"; 394 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MusicPlayer.app/MusicPlayer"; 395 | }; 396 | name = Debug; 397 | }; 398 | 0270482719C8E08B00FDA1C5 /* Release */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | BUNDLE_LOADER = "$(TEST_HOST)"; 402 | FRAMEWORK_SEARCH_PATHS = ( 403 | "$(SDKROOT)/Developer/Library/Frameworks", 404 | "$(inherited)", 405 | ); 406 | INFOPLIST_FILE = MusicPlayerTests/Info.plist; 407 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MusicPlayer.app/MusicPlayer"; 410 | }; 411 | name = Release; 412 | }; 413 | /* End XCBuildConfiguration section */ 414 | 415 | /* Begin XCConfigurationList section */ 416 | 027047FE19C8E08B00FDA1C5 /* Build configuration list for PBXProject "MusicPlayer" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | 0270482019C8E08B00FDA1C5 /* Debug */, 420 | 0270482119C8E08B00FDA1C5 /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | 0270482219C8E08B00FDA1C5 /* Build configuration list for PBXNativeTarget "MusicPlayer" */ = { 426 | isa = XCConfigurationList; 427 | buildConfigurations = ( 428 | 0270482319C8E08B00FDA1C5 /* Debug */, 429 | 0270482419C8E08B00FDA1C5 /* Release */, 430 | ); 431 | defaultConfigurationIsVisible = 0; 432 | defaultConfigurationName = Release; 433 | }; 434 | 0270482519C8E08B00FDA1C5 /* Build configuration list for PBXNativeTarget "MusicPlayerTests" */ = { 435 | isa = XCConfigurationList; 436 | buildConfigurations = ( 437 | 0270482619C8E08B00FDA1C5 /* Debug */, 438 | 0270482719C8E08B00FDA1C5 /* Release */, 439 | ); 440 | defaultConfigurationIsVisible = 0; 441 | defaultConfigurationName = Release; 442 | }; 443 | /* End XCConfigurationList section */ 444 | }; 445 | rootObject = 027047FB19C8E08B00FDA1C5 /* Project object */; 446 | } 447 | -------------------------------------------------------------------------------- /MusicPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MusicPlayer/APIController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIController.swift 3 | // MusicPlayer 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol APIControllerProtocol { 12 | func didReceiveAPIResults(results: NSDictionary) 13 | } 14 | 15 | class APIController { 16 | 17 | var delegate: APIControllerProtocol 18 | 19 | init(delegate: APIControllerProtocol) { 20 | self.delegate = delegate 21 | } 22 | 23 | func get(path: String) { 24 | let url = NSURL(string: path) 25 | let session = NSURLSession.sharedSession() 26 | let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in 27 | println("Task completed") 28 | if(error != nil) { 29 | // If there is an error in the web request, print it to the console 30 | println(error.localizedDescription) 31 | } 32 | var err: NSError? 33 | var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary 34 | if(err != nil) { 35 | // If there is an error parsing JSON, print it to the console 36 | println("JSON Error \(err!.localizedDescription)") 37 | } 38 | let results: NSArray = jsonResult["results"] as NSArray 39 | self.delegate.didReceiveAPIResults(jsonResult) // THIS IS THE NEW LINE!! 40 | }) 41 | task.resume() 42 | } 43 | 44 | func searchItunesFor(searchTerm: String) { 45 | 46 | // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs 47 | let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil) 48 | 49 | // Now escape anything else that isn't URL-friendly 50 | if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) { 51 | let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album" 52 | get(urlPath) 53 | } 54 | } 55 | 56 | func lookupAlbum(collectionId: Int) { 57 | get("https://itunes.apple.com/lookup?id=\(collectionId)&entity=song") 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /MusicPlayer/Album.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Album.swift 3 | // MusicPlayer 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Album { 12 | var title: String 13 | var price: String 14 | var thumbnailImageURL: String 15 | var largeImageURL: String 16 | var itemURL: String 17 | var artistURL: String 18 | var collectionId: Int 19 | 20 | init(name: String, price: String, thumbnailImageURL: String, largeImageURL: String, itemURL: String, artistURL: String, collectionId: Int) { 21 | self.title = name 22 | self.price = price 23 | self.thumbnailImageURL = thumbnailImageURL 24 | self.largeImageURL = largeImageURL 25 | self.itemURL = itemURL 26 | self.artistURL = artistURL 27 | self.collectionId = collectionId 28 | } 29 | 30 | class func albumsWithJSON(allResults: NSArray) -> [Album] { 31 | 32 | // Create an empty array of Albums to append to from this list 33 | var albums = [Album]() 34 | 35 | // Store the results in our table data array 36 | if allResults.count>0 { 37 | 38 | // Sometimes iTunes returns a collection, not a track, so we check both for the 'name' 39 | for result in allResults { 40 | 41 | var name = result["trackName"] as? String 42 | if name == nil { 43 | name = result["collectionName"] as? String 44 | } 45 | 46 | // Sometimes price comes in as formattedPrice, sometimes as collectionPrice.. and sometimes it's a float instead of a string. Hooray! 47 | var price = result["formattedPrice"] as? String 48 | if price == nil { 49 | price = result["collectionPrice"] as? String 50 | if price == nil { 51 | var priceFloat: Float? = result["collectionPrice"] as? Float 52 | var nf: NSNumberFormatter = NSNumberFormatter() 53 | nf.maximumFractionDigits = 2 54 | if priceFloat != nil { 55 | price = "$"+nf.stringFromNumber(priceFloat!) 56 | } 57 | } 58 | } 59 | 60 | let thumbnailURL = result["artworkUrl60"] as? String ?? "" 61 | let imageURL = result["artworkUrl100"] as? String ?? "" 62 | let artistURL = result["artistViewUrl"] as? String ?? "" 63 | 64 | var itemURL = result["collectionViewUrl"] as? String 65 | if itemURL == nil { 66 | itemURL = result["trackViewUrl"] as? String 67 | } 68 | 69 | var collectionId = result["collectionId"] as? Int 70 | 71 | var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL, largeImageURL: imageURL, itemURL: itemURL!, artistURL: artistURL, collectionId: collectionId!) 72 | albums.append(newAlbum) 73 | 74 | } 75 | } 76 | return albums 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /MusicPlayer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MusicPlayer 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. 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 | println("Hello World!") 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 | -------------------------------------------------------------------------------- /MusicPlayer/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 | -------------------------------------------------------------------------------- /MusicPlayer/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 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 105 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /MusicPlayer/Blank52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jquave/Swift-Tutorial/f18eb8d342ad36fc0a2b494df1ebd2eec891802d/MusicPlayer/Blank52.png -------------------------------------------------------------------------------- /MusicPlayer/DetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsViewController.swift 3 | // MusicPlayer 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MediaPlayer 11 | 12 | class DetailsViewController: UIViewController, APIControllerProtocol, UITableViewDelegate, UITableViewDataSource { 13 | 14 | var album: Album? 15 | var tracks = [Track]() 16 | 17 | @IBOutlet weak var titleLabel: UILabel! 18 | @IBOutlet weak var albumCover: UIImageView! 19 | @IBOutlet weak var tracksTableView: UITableView! 20 | lazy var api : APIController = APIController(delegate: self) 21 | var mediaPlayer: MPMoviePlayerController = MPMoviePlayerController() 22 | 23 | required init(coder aDecoder: NSCoder) { 24 | super.init(coder: aDecoder) 25 | } 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | titleLabel.text = self.album?.title 30 | albumCover.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album!.largeImageURL))) 31 | 32 | if self.album != nil { 33 | api.lookupAlbum(self.album!.collectionId) 34 | } 35 | } 36 | 37 | // MARK: UITableViewDataSource 38 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 39 | return tracks.count 40 | } 41 | 42 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 43 | let cell = tableView.dequeueReusableCellWithIdentifier("TrackCell") as TrackCell 44 | let track = tracks[indexPath.row] 45 | cell.titleLabel.text = track.title 46 | cell.playIcon.text = "▶️" 47 | 48 | return cell 49 | } 50 | 51 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 52 | var track = tracks[indexPath.row] 53 | mediaPlayer.stop() 54 | mediaPlayer.contentURL = NSURL(string: track.previewUrl) 55 | mediaPlayer.play() 56 | if let cell = tableView.cellForRowAtIndexPath(indexPath) as? TrackCell { 57 | cell.playIcon.text = "◾️" 58 | } 59 | } 60 | 61 | func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { 62 | cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1) 63 | UIView.animateWithDuration(0.25, animations: { 64 | cell.layer.transform = CATransform3DMakeScale(1,1,1) 65 | }) 66 | } 67 | 68 | // MARK: APIControllerProtocol 69 | func didReceiveAPIResults(results: NSDictionary) { 70 | var resultsArr: NSArray = results["results"] as NSArray 71 | dispatch_async(dispatch_get_main_queue(), { 72 | self.tracks = Track.tracksWithJSON(resultsArr) 73 | self.tracksTableView.reloadData() 74 | UIApplication.sharedApplication().networkActivityIndicatorVisible = false 75 | }) 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /MusicPlayer/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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /MusicPlayer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.jqsoftware.$(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 | 40 | 41 | -------------------------------------------------------------------------------- /MusicPlayer/SearchResultsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MusicPlayer 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, APIControllerProtocol { 12 | 13 | @IBOutlet var appsTableView : UITableView? 14 | var albums = [Album]() 15 | var api : APIController? 16 | var imageCache = [String : UIImage]() 17 | let kCellIdentifier: String = "SearchResultCell" 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | api = APIController(delegate: self) 22 | UIApplication.sharedApplication().networkActivityIndicatorVisible = true 23 | api!.searchItunesFor("Beatles") 24 | } 25 | 26 | override func didReceiveMemoryWarning() { 27 | super.didReceiveMemoryWarning() 28 | // Dispose of any resources that can be recreated. 29 | } 30 | 31 | 32 | // MARK: UITableViewDataSource 33 | 34 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 35 | return albums.count 36 | } 37 | 38 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 39 | 40 | let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as UITableViewCell 41 | 42 | let album = self.albums[indexPath.row] 43 | cell.textLabel?.text = album.title 44 | cell.imageView?.image = UIImage(named: "Blank52") 45 | 46 | // Get the formatted price string for display in the subtitle 47 | let formattedPrice = album.price 48 | 49 | // Grab the artworkUrl60 key to get an image URL for the app's thumbnail 50 | let urlString = album.thumbnailImageURL 51 | 52 | // Check our image cache for the existing key. This is just a dictionary of UIImages 53 | //var image: UIImage? = self.imageCache.valueForKey(urlString) as? UIImage 54 | var image = self.imageCache[urlString] 55 | 56 | 57 | if( image == nil ) { 58 | // If the image does not exist, we need to download it 59 | var imgURL: NSURL = NSURL(string: urlString) 60 | 61 | // Download an NSData representation of the image at the URL 62 | let request: NSURLRequest = NSURLRequest(URL: imgURL) 63 | NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in 64 | if error == nil { 65 | image = UIImage(data: data) 66 | 67 | // Store the image in to our cache 68 | self.imageCache[urlString] = image 69 | dispatch_async(dispatch_get_main_queue(), { 70 | if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) { 71 | cellToUpdate.imageView?.image = image 72 | } 73 | }) 74 | } 75 | else { 76 | println("Error: \(error.localizedDescription)") 77 | } 78 | }) 79 | 80 | } 81 | else { 82 | dispatch_async(dispatch_get_main_queue(), { 83 | if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) { 84 | cellToUpdate.imageView?.image = image 85 | } 86 | }) 87 | } 88 | 89 | cell.detailTextLabel?.text = formattedPrice 90 | 91 | return cell 92 | } 93 | 94 | func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { 95 | cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1) 96 | UIView.animateWithDuration(0.25, animations: { 97 | cell.layer.transform = CATransform3DMakeScale(1,1,1) 98 | }) 99 | } 100 | 101 | 102 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 103 | var detailsViewController: DetailsViewController = segue.destinationViewController as DetailsViewController 104 | var albumIndex = appsTableView!.indexPathForSelectedRow()!.row 105 | var selectedAlbum = self.albums[albumIndex] 106 | detailsViewController.album = selectedAlbum 107 | } 108 | 109 | func didReceiveAPIResults(results: NSDictionary) { 110 | var resultsArr: NSArray = results["results"] as NSArray 111 | dispatch_async(dispatch_get_main_queue(), { 112 | self.albums = Album.albumsWithJSON(resultsArr) 113 | self.appsTableView!.reloadData() 114 | UIApplication.sharedApplication().networkActivityIndicatorVisible = false 115 | }) 116 | } 117 | 118 | } 119 | 120 | -------------------------------------------------------------------------------- /MusicPlayer/Track.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Track.swift 3 | // MusicPlayer 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | class Track { 11 | 12 | var title: String 13 | var price: String 14 | var previewUrl: String 15 | var tracks = [Track]() 16 | 17 | init(title: String, price: String, previewUrl: String) { 18 | self.title = title 19 | self.price = price 20 | self.previewUrl = previewUrl 21 | } 22 | 23 | class func tracksWithJSON(allResults: NSArray) -> [Track] { 24 | 25 | var tracks = [Track]() 26 | 27 | if allResults.count>0 { 28 | for trackInfo in allResults { 29 | // Create the track 30 | if let kind = trackInfo["kind"] as? String { 31 | if kind=="song" { 32 | 33 | var trackPrice = trackInfo["trackPrice"] as? String 34 | var trackTitle = trackInfo["trackName"] as? String 35 | var trackPreviewUrl = trackInfo["previewUrl"] as? String 36 | 37 | if(trackTitle == nil) { 38 | trackTitle = "Unknown" 39 | } 40 | else if(trackPrice == nil) { 41 | println("No trackPrice in \(trackInfo)") 42 | trackPrice = "?" 43 | } 44 | else if(trackPreviewUrl == nil) { 45 | trackPreviewUrl = "" 46 | } 47 | 48 | var track = Track(title: trackTitle!, price: trackPrice!, previewUrl: trackPreviewUrl!) 49 | tracks.append(track) 50 | 51 | } 52 | } 53 | } 54 | } 55 | return tracks 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /MusicPlayer/TrackCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackCell.swift 3 | // MusicPlayer 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TrackCell: UITableViewCell { 12 | 13 | @IBOutlet weak var playIcon: UILabel! 14 | @IBOutlet weak var titleLabel: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | override func setSelected(selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | 24 | // Configure the view for the selected state 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /MusicPlayerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.jqsoftware.$(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 | -------------------------------------------------------------------------------- /MusicPlayerTests/MusicPlayerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MusicPlayerTests.swift 3 | // MusicPlayerTests 4 | // 5 | // Created by Jameson Quave on 9/16/14. 6 | // Copyright (c) 2014 JQ Software LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class MusicPlayerTests: 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 | --------------------------------------------------------------------------------