{"id":735,"date":"2014-06-12T17:32:01","date_gmt":"2014-06-12T23:32:01","guid":{"rendered":"http:\/\/jamesonquave.com\/blog\/?p=735"},"modified":"2015-04-17T09:22:39","modified_gmt":"2015-04-17T15:22:39","slug":"developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells","status":"publish","type":"post","link":"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/","title":{"rendered":"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7)"},"content":{"rendered":"<p><strong><i>This section completely updated to reflect changes in Xcode 6.3, as of April 17, 2015<\/i><\/strong><\/b><\/p>\n<p>In parts 1 through 6 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven&#8217;t read that yet, check out <a title=\"Developing iOS Apps Using Swift Tutorial Part 1\" href=\"http:\/\/jamesonquave.com\/blog\/developing-ios-apps-using-swift-tutorial\/\">Part 1<\/a><\/p>\n<p>In this tutorial we&#8217;re going to implement an Album detail view, that makes a second API call to retrieve a list of tracks for an album, downloads higher resolution album art, and allows of to play previews of the tracks within our app. As an optional extra, we are going to also implement some nifty animations using the Core Animation API provided by the iOS SDK. When we&#8217;re done, we&#8217;re going to have something like this (video taken in iOS 7 Simulator)<\/p>\n<p><iframe loading=\"lazy\" width=\"480\" height=\"360\" src=\"\/\/www.youtube.com\/embed\/9Jxg_b2t2lY\" frameborder=\"0\" allowfullscreen><\/iframe><\/p>\n<p><strong>Setting up our API Controller<\/strong><\/p>\n<p>Because we&#8217;re going to be adding additional API calls in this part of the tutorial, we should modify our API Controller for some code re-use. Let&#8217;s start with a more generic get request.<\/p>\n<p>In your API Controller add the function get(), which takes path as a String argument, and converts it to an NSURL:<\/p>\n<pre class=\"brush: js;\">\r\nfunc get(path: String) {\r\n    let url = NSURL(string: path)\r\n    ...\r\n<\/pre>\n<p>Now get the NSURLSession and send it using dataTaskWithURL as we did before, in fact the code is exactly the same as what is currently inside of our searchItunesFor() function, so just copy and paste it from there. Start cutting right after the line<\/p>\n<pre class=\"brush: js;\">\r\nlet urlPath = \"https:\/\/itunes.apple.com\/search?term=\\(escapedSearchTerm)&media=music&entity=album\"\r\n<\/pre>\n<p>And move everything in to the get() method. Your complete APIController.swift file should look something like this now:<\/p>\n<pre class=\"brush: js; highlight: [14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,45,46,47];\">\r\nimport Foundation\r\n\r\nprotocol APIControllerProtocol {\r\n    func didReceiveAPIResults(results: NSArray)\r\n}\r\n\r\nclass APIController {\r\n    var delegate: APIControllerProtocol\r\n    \r\n    init(delegate: APIControllerProtocol) {\r\n        self.delegate = delegate\r\n    }\r\n    \r\n    func get(path: String) {\r\n        let url = NSURL(string: path)\r\n        let session = NSURLSession.sharedSession()\r\n        let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in\r\n            println(\"Task completed\")\r\n            if(error != nil) {\r\n                \/\/ If there is an error in the web request, print it to the console\r\n                println(error.localizedDescription)\r\n            }\r\n            var err: NSError?\r\n            if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {\r\n                if(err != nil) {\r\n                    \/\/ If there is an error parsing JSON, print it to the console\r\n                    println(\"JSON Error \\(err!.localizedDescription)\")\r\n                }\r\n                if let results: NSArray = jsonResult[\"results\"] as? NSArray {\r\n                    self.delegate.didReceiveAPIResults(results)\r\n                }\r\n            }\r\n        })\r\n        \r\n        \/\/ The task is just an object with all these properties set\r\n        \/\/ In order to actually make the web request, we need to \"resume\"\r\n        task.resume()\r\n    }\r\n    \r\n    func searchItunesFor(searchTerm: String) {\r\n        \/\/ The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs\r\n        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(\" \", withString: \"+\", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)\r\n        \r\n        \/\/ Now escape anything else that isn't URL-friendly\r\n        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {\r\n            let urlPath = \"https:\/\/itunes.apple.com\/search?term=\\(escapedSearchTerm)&amp;media=music&amp;entity=album\"\r\n        }\r\n    }\r\n    \r\n}\r\n<\/pre>\n<p>Now in our searchItunesFor function, we can simply call on our new get() function and slim it down to the bare essentials. Just add a call to the get(urlPath) method on the end. The final method should look like this:<\/p>\n<pre class=\"brush: js; highlight: [8];\">\r\nfunc searchItunesFor(searchTerm: String) {\r\n    \/\/ The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs\r\n    let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(\" \", withString: \"+\", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)\r\n    \r\n    \/\/ Now escape anything else that isn't URL-friendly\r\n    if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {\r\n        let urlPath = \"https:\/\/itunes.apple.com\/search?term=\\(escapedSearchTerm)&amp;media=music&amp;entity=album\"\r\n        get(urlPath)\r\n    }\r\n}\r\n<\/pre>\n<p>See the difference? The only part that was specific to the search function was the escaping of search terms, and embedding the term inside of the URL, so there&#8217;s no reason not to just break the get() part out in to it&#8217;s own method.<\/p>\n<p>Now, we can quickly add a second API function to lookup a specific album. But first, let&#8217;s modify our album model to store a collectionId variable, used by iTunes to identify individual albums.<\/p>\n<p>In our Album struct, add a new variable collectionId of type Int.<\/p>\n<pre class=\"brush: js;\">\r\nlet collectionId: Int\r\n<\/pre>\n<p>..modify the constructor to accept collectionId as an argument, and add a line to set the collectionId as one of our variables being passed in through init()<\/p>\n<pre class=\"brush: js; highlight: [1,8];\">init(name: String, price: String, thumbnailImageURL: String, largeImageURL: String, itemURL: String, artistURL: String, collectionId: Int) {\r\n    self.title = name\r\n    self.price = price\r\n    self.thumbnailImageURL = thumbnailImageURL\r\n    self.largeImageURL = largeImageURL\r\n    self.itemURL = itemURL\r\n    self.artistURL = artistURL\r\n    self.collectionId = collectionId\r\n}\r\n<\/pre>\n<p>Great! We can now initialize Albums with a collectionId, but now our existing albumsWithJSON code is wrong, it&#8217;s missing the collectionId parameter.<br \/>\nFind the line that creates the newAlbum just before it appends to the array returned from albumsWithJSON().<\/p>\n<p>Modify this to get the collectionId our of the result dictionary, and pass it in to the Album constructor. Since we really need the collectionId to not be nil in order for this app to work, we&#8217;ll bundle the whole album creation inside of an if let clause so that only valid albums will show up on the list.<\/p>\n<pre class=\"brush: js;\">\r\nif let collectionId = result[\"collectionId\"] as? Int {\r\n    var newAlbum = Album(name: name!,\r\n        price: price!,\r\n        thumbnailImageURL: thumbnailURL,\r\n        largeImageURL: imageURL,\r\n        itemURL: itemURL!,\r\n        artistURL: artistURL,\r\n        collectionId: collectionId)\r\n    albums.append(newAlbum)\r\n}\r\n<\/pre>\n<p>The reason we need to add this collectionId variable is so that we can perform lookups of albums when they are selected. With the collectionId, it&#8217;s easy to do a second query of the iTunes API to gather lots of details about an individual album. For example, we can get a list of tracks with media URLs that will give us a 30 second preview.<\/p>\n<hr \/>\n<p><strong>Setting up the Details View<\/strong><\/p>\n<p>In the last tutorial we added a DetailsViewController to our storyboard. Let&#8217;s add a TableView to this view as well. You can lay it out however you like, but I recommend giving the Table View the majority of the screen space. This is where we&#8217;re going to load in our list of tracks.<br \/>\n<img src='http:\/\/jamesonquave.com\/tutImg\/DetailView.png' \/><\/p>\n<p>Let&#8217;s now connect this new TableView to a property in DetailsViewController called tracksTableView.<\/p>\n<p><img src='http:\/\/jamesonquave.com\/tutImg\/ConnectDetails.png' \/><\/p>\n<pre class=\"brush: js;\">\r\n@IBOutlet weak var tracksTableView: UITableView!\r\n<\/pre>\n<p>Now, set the dataSource and delegate of the table view to the DetailsViewController, and implement the protocol as we did before:<\/p>\n<pre class=\"brush: js;\">\r\nfunc tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {\r\n    return 0\r\n}\r\n\r\nfunc tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {\r\n    return UITableViewCell()\r\n}\r\n<\/pre>\n<p>It&#8217;s probably useful at this point to try and run the app. You should be able to drill in to an album and see an empty list of tracks.<\/p>\n<hr \/>\n<p>Everything working? Cool, let&#8217;s keep going&#8230;<\/p>\n<p>If we&#8217;re going to show tracks we&#8217;re going to need another model. Create a new Swift file called &#8216;Track.swift&#8217;, and give it three String properties for title, price, and previewUrl.<\/p>\n<pre class=\"brush: js;\">\r\nimport Foundation\r\nstruct Track {\r\n    let title: String\r\n    let price: String\r\n    let previewUrl: String\r\n    \r\n    init(title: String, price: String, previewUrl: String) {\r\n        self.title = title\r\n        self.price = price\r\n        self.previewUrl = previewUrl\r\n    }\r\n}\r\n<\/pre>\n<p>This model is set up in pretty much exactly the same way as the Album model, not much new here.<\/p>\n<p>In DetailsViewController, let&#8217;s add an array of tracks as a new property.<\/p>\n<pre class=\"brush: js;\">\r\nvar tracks = [Track]()\r\n<\/pre>\n<p>Now, to get track information for the album, we need to modify our API Controller again. Fortunately for us, we have an easy to use get() function that makes this pretty simple.<\/p>\n<p>Let&#8217;s add a new function to APIController that takes an Int collectionId argument, and tell it to use get() to get track information<\/p>\n<pre class=\"brush: js;\">\r\nfunc lookupAlbum(collectionId: Int) {\r\n    get(\"https:\/\/itunes.apple.com\/lookup?id=\\(collectionId)&entity=song\")\r\n}<\/pre>\n<p>We&#8217;re going to need to use this in our DetailsViewController, so we now need to implement the APIControllerProtocol we wrote earlier in to DetailsViewController. So modify the class definition of DetailsViewController to include this, and our api object.<\/p>\n<pre class=\"brush: js;\">\r\nclass DetailsViewController: UIViewController, APIControllerProtocol {\r\n    lazy var api : APIController = APIController(delegate: self)\r\n    ...\r\n<\/pre>\n<p>Your project will have an error at this point about the protocol we haven&#8217;t yet implemented, but that&#8217;s ok let&#8217;s keep moving.<\/p>\n<p>In the DetailsViewController viewDidLoad method, we want to add a portion to pull down tracks based on the selected album, so let&#8217;s add the following:<\/p>\n<pre class=\"brush: js;\">\r\n\/\/ Load in tracks\r\nif self.album != nil {\r\n    api.lookupAlbum(self.album!.collectionId)\r\n}<\/pre>\n<p>This is all stuff we&#8217;ve seen before. We create an instance of our APIController with the delegate set to self, and use our new lookupTrack method to get details on the tracks in the selected album. Here we use the lazy keyword to indicate we don&#8217;t want the APIController instance api to be instantiated until it is used. We need to do this to avoid the circular dependency of DetailsViewController needing to be initialized to pass it in as an argument to the APIController(delegate:) constructor. Earlier we used an optional APIController to solve this problem, but using the lazy keyword is another way to solve this problem and it&#8217;s a little cleaner.<\/p>\n<p>To fully adhere to our APIControllerProtocol, we need to implement the didReceiveAPIResults() function in this class too. We&#8217;ll use this to load in our track data. We&#8217;ll implement this exactly as we did for the SearchResultsViewController, by offloading the responsibility of converting the JSON response in to a list of tracks to the Track model.<\/p>\n<pre class=\"brush: js;\">\r\n\/\/ MARK: APIControllerProtocol\r\nfunc didReceiveAPIResults(results: NSArray) {\r\n    dispatch_async(dispatch_get_main_queue(), {\r\n        self.tracks = Track.tracksWithJSON(results)\r\n        self.tracksTableView.reloadData()\r\n        UIApplication.sharedApplication().networkActivityIndicatorVisible = false\r\n    })\r\n}<\/pre>\n<p>We&#8217;re using a non-existent tracksWithJSON() static method on Track. So we need to add that before this will compile. Open up Track.swift and add a method similar to our albumsWithJSON method.<\/p>\n<pre class=\"brush: js;\">\r\nstatic func tracksWithJSON(results: NSArray) -> [Track] {\r\n    var tracks = [Track]()\r\n    for trackInfo in results {\r\n        \/\/ Create the track\r\n        if let kind = trackInfo[\"kind\"] as? String {\r\n            if kind==\"song\" {\r\n                var trackPrice = trackInfo[\"trackPrice\"] as? String\r\n                var trackTitle = trackInfo[\"trackName\"] as? String\r\n                var trackPreviewUrl = trackInfo[\"previewUrl\"] as? String\r\n                if(trackTitle == nil) {\r\n                    trackTitle = \"Unknown\"\r\n                }\r\n                else if(trackPrice == nil) {\r\n                    println(\"No trackPrice in \\(trackInfo)\")\r\n                    trackPrice = \"?\"\r\n                }\r\n                else if(trackPreviewUrl == nil) {\r\n                    trackPreviewUrl = \"\"\r\n                }\r\n                var track = Track(title: trackTitle!, price: trackPrice!, previewUrl: trackPreviewUrl!)\r\n                tracks.append(track)\r\n            }\r\n        }\r\n    }\r\n    return tracks\r\n}\r\n<\/pre>\n<p>This API call returns the album before it returns the list of tracks, so we also add a check to make sure the &#8220;kind&#8221; key is set to &#8220;song&#8221;, as you see on line 8. Otherwise this function is just extracting some data from the JSON; Then we check that the three fields we need aren&#8217;t null, and if so set some reasonable defaults.<\/p>\n<p>Now in DetailsViewController let&#8217;s modify the numberOfRowsInSection to be the track count<\/p>\n<pre class=\"brush: js;\">\r\nfunc tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {\r\n    return tracks.count\r\n}<\/pre>\n<p>And let&#8217;s modify the cellForRowAtIndexPath method to load in our track data.<\/p>\n<p>First, we need to add a prototype cell to the TableView in our storyboard, because we&#8217;re going to use a custom cell.<br \/>\nSo select the Table View in the storyboard, and set the number of prototype cells to 1.<br \/>\nThen, select the cell itself and set the Identifier to &#8220;TrackCell&#8221; in the Attributes Inspector (on right-hand panel while selecting the Table View.)<\/p>\n<p><img src='http:\/\/jamesonquave.com\/tutImg\/TrackCellID.png' \/><\/p>\n<p><strong>Adding a Custom Table View Cell<\/strong><\/p>\n<p>To demonstrate what the prototype cells are really for, I think we should add some custom controls to this one. Create a new Swift class called TrackCell that inherits from UITableViewCell, and give it two IBOutlet UILabels called playIcon and titleLabel.<\/p>\n<p>Now, back in your Storyboard file. Change the prototype cell&#8217;s class to &#8216;TrackCell&#8217; under the Identity Inspector in the right-hand panel.<br \/>\nNext, add two UILabel&#8217;s to the cell by dragging the views on to the cell itself. Put one on the left for our play\/pause button, and one taking up most of the space on the right to say the title of the track.<\/p>\n<p>Drag two labels on to the prototype cell. Make one of them small and on the left, around 23&#215;23 points, for a &#8216;Play\/Stop&#8217; icon. The second one will be the track title and should take up the rest of the cell. Click in to your play button label and then in the Mac OS menu bar hit Edit->Emoji &#038; Symbols and find a play button looking icon. I found some under Emoji->Objects &#038; Symbols. <i>As an optional challenge, try using an image for the button icon!<\/i><\/p>\n<p><img src='http:\/\/jamesonquave.com\/tutImg\/ConnectTrackCell.png' \/><\/p>\n<pre class=\"brush: js;\">\r\nimport UIKit\r\nclass TrackCell: UITableViewCell {\r\n    @IBOutlet weak var playIcon: UILabel!\r\n    @IBOutlet weak var titleLabel: UILabel!\r\n}\r\n<\/pre>\n<p>When you&#8217;re done you should have a prototype cell looking something like this:<br \/>\n<img src='http:\/\/jamesonquave.com\/labelPrototype.gif' \/><\/p>\n<p>In the DetailsViewController, we can now implement the custom cells by getting the TrackCell object and casting it to our class with &#8216;as TrackCell&#8217;<\/p>\n<pre class=\"brush: js;\">\r\nfunc tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {\r\n    let cell = tableView.dequeueReusableCellWithIdentifier(\"TrackCell\") as! TrackCell\r\n    let track = tracks[indexPath.row]\r\n    cell.titleLabel.text = track.title\r\n    cell.playIcon.text = \"YOUR_PLAY_ICON\"\r\n    return cell\r\n}\r\n<\/pre>\n<p>The logic is mostly the same as our other table view, with the exception that we cast cell to our custom class, TrackCell, on the first line. The &#8220;YOUR_PLAY_ICON&#8221; text should be replaced with the play icon, which again, you can get by hitting Edit->Emoji &#038; Symbols in the Mac OS menu bar. <i>Don&#8217;t forget to put quotes around it!<\/i><\/p>\n<p>Next we grab the track we need from our tracks array, just as before with albums.<\/p>\n<p>Finally we access our custom IBOutlet variable, titleLabel, set it&#8217;s text to be the track title, and do the same with playIcon.<\/p>\n<p><strong><br \/>\nCongrats on getting this far, we&#8217;re in the home stretch!<br \/>\n<\/strong><\/p>\n<hr \/>\n<p><strong>Play some music<\/strong><\/p>\n<p>Okay, next we want to set up a way to actually hear some audio. We&#8217;re going to use the MPMoviePlayerController class to do this. It&#8217;s easy to work with, and works just fine with audio-only streams.<\/p>\n<p>First off, in our DetailsViewController class let&#8217;s add the mediaPlayer as a property, right under the class definition add:<\/p>\n<pre class=\"brush: js;\">\r\nvar mediaPlayer: MPMoviePlayerController = MPMoviePlayerController()\r\n<\/pre>\n<p><b>ERROR! Use of undeclared type MPMoviePlayerController.<\/b><\/p>\n<p>It&#8217;s okay, this is just because we need to import the framework <strong>MediaPlayer<\/strong>, it isn&#8217;t included by default in our project.<\/p>\n<p>Just add the following to the top of your DetailsViewController:<\/p>\n<pre class=\"brush: js;\">\r\nimport MediaPlayer\r\n<\/pre>\n<p>Next, let&#8217;s kick off the audio playing when a user selects one of the track&#8217;s rows. Add the following to our DetailsViewController:<\/p>\n<pre class=\"brush: js;\">\r\nfunc tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {\r\n    var track = tracks[indexPath.row]\r\n    mediaPlayer.stop()\r\n    mediaPlayer.contentURL = NSURL(string: track.previewUrl)\r\n    mediaPlayer.play()\r\n    if let cell = tableView.cellForRowAtIndexPath(indexPath) as? TrackCell {\r\n        cell.playIcon.text = \"YOUR_STOP_ICON\"\r\n    }\r\n}\r\n<\/pre>\n<p>The line mediaPlayer.stop() stop&#8217;s the currently playing track. If there isn&#8217;t one playing, nothing happens. We don&#8217;t want to play multiple tracks at once so let&#8217;s make sure we stop a track if another one is clicked \ud83d\ude42<\/p>\n<p>Next, mediaPlayer.contentURL sets a url for where the media player should load it&#8217;s content. In our case it&#8217;s from the url stored in track.previewUrl.<\/p>\n<p>Finally, we call mediaPlayer.play(), and get the track cell for the tapped row index.<br \/>\nIf this row is still visible, it&#8217;ll set &#8216;cell&#8217; and here we can change the playIcon label to instead show the stopped icon, which we set again by using Edit->Emoji &#038; Symbols on the Mac OS menu bar.<\/p>\n<p>If you run your app, you should now have a fully working iTunes music preview application! This by itself is pretty great, but let&#8217;s add one more thing to make it even more slick, some smooth table view cell animations.<\/p>\n<p><strong>Adding Animations<\/strong><\/p>\n<p>This is actually really easy, and has a very cool visual effect.<\/p>\n<p>All we&#8217;re going to do is add the following function to both our SearchResultsViewController, and our DetailsViewController:<\/p>\n<pre class=\"brush: js;\">\r\nfunc tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {\r\n    cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)\r\n    UIView.animateWithDuration(0.25, animations: {\r\n        cell.layer.transform = CATransform3DMakeScale(1,1,1)\r\n        })\r\n}\r\n<\/pre>\n<p>Now run the app and scroll around, neat right?<\/p>\n<p>So how&#8217;s it work?<br \/>\nThe function willDisplayCell is called from the TableView delegate, similar to the rest of our callback functions that set up the row. But this one is only called the moment before a cell appears on-screen, either through initial loading or through scrolling.<\/p>\n<pre class=\"brush: js;\">\r\ncell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)\r\n<\/pre>\n<p>This first line uses CATransform3DMakeScale() to create a transform matrix that scales down any object in x, y, and z. If you are familiar with linear algebra you&#8217;ll know what this means right away. If not, it&#8217;s not super important. The point is, it makes things scale, and here we&#8217;re scaling it down to 10% by setting the x and y values to 0.1.<\/p>\n<p>So, we are basically just setting the cell layer&#8217;s transform to be 90% smaller.<\/p>\n<p>Next we set the cell layer&#8217;s transform to a new scale, this time of (1,1,1). This just means that it should return to it&#8217;s original scale. Because this line is run inside of the animateWithDuration() block, we get the animation for free courtesy of Core Animation.<\/p>\n<p>Experienced Obj-C developers will probably recognize this is not the only way to perform such an animation. However, I believe this method is the easiest to understand, in addition to being the most Swifty.<\/p>\n<p>In my <a href='http:\/\/jamesonquave.com\/swiftebook\/'>upcoming book<\/a> I go in to great detail about how to efficiently use Core Animation to make beautiful animations for your apps. Using Core Animation in this way really makes your app pop.<\/p>\n<p>The full source code for this section is <a href='https:\/\/github.com\/jquave\/Swift-Tutorial\/tree\/Part7'>available here<\/a>.<\/p>\n<p>A reader of this tutorial series contributed the next section, which covers producing a nicer play\/pause icon purely in code. <a href='http:\/\/jamesonquave.com\/blog\/drawing-custom-views-with-swift-andrew-vanwagoner\/'>Check it out here<\/a>.<br \/>\nMake sure to <a href=\"http:\/\/jamesonquave.us6.list-manage.com\/subscribe\/post?u=1d2576bf288fe2fd7fa71bd20&amp;id=6c787ed58a\">sign up to be notified<\/a> of the next tutorial series.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This section completely updated to reflect changes in Xcode 6.3, as of April 17, 2015 In parts 1 through 6 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven&#8217;t read that&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_links_to":"","_links_to_target":""},"categories":[26,21,25,10,5,32],"tags":[42,39,40,34,41,33,36,16],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v19.13 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7) - Jameson Quave<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7) - Jameson Quave\" \/>\n<meta property=\"og:description\" content=\"This section completely updated to reflect changes in Xcode 6.3, as of April 17, 2015 In parts 1 through 6 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven&#8217;t read that...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/\" \/>\n<meta property=\"og:site_name\" content=\"Jameson Quave\" \/>\n<meta property=\"article:published_time\" content=\"2014-06-12T23:32:01+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2015-04-17T15:22:39+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/jamesonquave.com\/tutImg\/DetailView.png\" \/>\n<meta name=\"author\" content=\"Jameson Quave\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jameson Quave\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/\",\"url\":\"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/\",\"name\":\"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7) - Jameson Quave\",\"isPartOf\":{\"@id\":\"https:\/\/jamesonquave.com\/blog\/#website\"},\"datePublished\":\"2014-06-12T23:32:01+00:00\",\"dateModified\":\"2015-04-17T15:22:39+00:00\",\"author\":{\"@id\":\"https:\/\/jamesonquave.com\/blog\/#\/schema\/person\/db6184f355c7f4e3b876d0f228c2fcfc\"},\"breadcrumb\":{\"@id\":\"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/jamesonquave.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/jamesonquave.com\/blog\/#website\",\"url\":\"https:\/\/jamesonquave.com\/blog\/\",\"name\":\"Jameson Quave\",\"description\":\"Using computer technology to educate, and improve lives.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/jamesonquave.com\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/jamesonquave.com\/blog\/#\/schema\/person\/db6184f355c7f4e3b876d0f228c2fcfc\",\"name\":\"Jameson Quave\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/jamesonquave.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d9786c83345117d560bbeab0e1f26814?s=96&d=retro&r=pg\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d9786c83345117d560bbeab0e1f26814?s=96&d=retro&r=pg\",\"caption\":\"Jameson Quave\"},\"sameAs\":[\"http:\/\/jamesonquave.com\"],\"url\":\"https:\/\/jamesonquave.com\/blog\/author\/jquave\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7) - Jameson Quave","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/","og_locale":"en_US","og_type":"article","og_title":"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7) - Jameson Quave","og_description":"This section completely updated to reflect changes in Xcode 6.3, as of April 17, 2015 In parts 1 through 6 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven&#8217;t read that...","og_url":"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/","og_site_name":"Jameson Quave","article_published_time":"2014-06-12T23:32:01+00:00","article_modified_time":"2015-04-17T15:22:39+00:00","og_image":[{"url":"http:\/\/jamesonquave.com\/tutImg\/DetailView.png"}],"author":"Jameson Quave","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Jameson Quave","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/","url":"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/","name":"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7) - Jameson Quave","isPartOf":{"@id":"https:\/\/jamesonquave.com\/blog\/#website"},"datePublished":"2014-06-12T23:32:01+00:00","dateModified":"2015-04-17T15:22:39+00:00","author":{"@id":"https:\/\/jamesonquave.com\/blog\/#\/schema\/person\/db6184f355c7f4e3b876d0f228c2fcfc"},"breadcrumb":{"@id":"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/jamesonquave.com\/blog\/developing-ios-8-apps-using-swift-animations-audio-and-custom-table-view-cells\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/jamesonquave.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Developing iOS 8 Apps Using Swift \u2013 Animations, Audio, and Custom Table View Cells (Part 7)"}]},{"@type":"WebSite","@id":"https:\/\/jamesonquave.com\/blog\/#website","url":"https:\/\/jamesonquave.com\/blog\/","name":"Jameson Quave","description":"Using computer technology to educate, and improve lives.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/jamesonquave.com\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/jamesonquave.com\/blog\/#\/schema\/person\/db6184f355c7f4e3b876d0f228c2fcfc","name":"Jameson Quave","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/jamesonquave.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d9786c83345117d560bbeab0e1f26814?s=96&d=retro&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d9786c83345117d560bbeab0e1f26814?s=96&d=retro&r=pg","caption":"Jameson Quave"},"sameAs":["http:\/\/jamesonquave.com"],"url":"https:\/\/jamesonquave.com\/blog\/author\/jquave\/"}]}},"_links":{"self":[{"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/posts\/735"}],"collection":[{"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/comments?post=735"}],"version-history":[{"count":79,"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/posts\/735\/revisions"}],"predecessor-version":[{"id":1780,"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/posts\/735\/revisions\/1780"}],"wp:attachment":[{"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/media?parent=735"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/categories?post=735"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jamesonquave.com\/blog\/wp-json\/wp\/v2\/tags?post=735"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}