Show Menu

Need an iOS Developer?

Submit your 30 day Job Listing for FREE

In the previous iOS8 Swift Tutorials we went over some basics of the iOS8 programming language: Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them.

This article is part of the create iOS8 Applications with Swift tutorial series, here are the other published articles, if you haven’t read them yet, check them out:

    In this tutorial we are going to implement an Album detail view which makes a second API call to retrieve a list of tracks for an album, downloads higher resolution album art, and allows us 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’re done, we’re going to have something like this (video taken in iOS 7 Simulator due to Apple NDA)

    Type Inference in Swift

    First off though, I want to talk about type inference in Swift. In many of our past tutorials we’ve been specifying the types of our variables, even though the Swift compiler could have told us the type based on what we’re initialising our variables to. As an example take a look at this line:

    
    let jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
    

    On the left-hand side we are specifying jsonResult is an NSDictionary, and on the right-hand side we’re casting the result of JSONObjectWithData to type NSDictionary, this means the compiler *already knows* that jsonResult will be an NSDictionary type, and therefore we can just omit it, like so:

    
    let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
    

    This allows us to clean up some of our code a bit and remove all the type specifiers where they aren’t needed. I went through and found 15 instances where we are specifying the type. As an exercise in moving towards a more Swifty code base I recommend doing the same in your projects, or you can simply check out from this commit of my version of the code to start off this tutorial.

    Setting up our API Controller

    Because we’re going to be adding additional API calls in this part of the Swift tutorial, we should modify our API Controller for some code re-use. Let’s start with a more generic get request.

    In your API Controller add the function get(), which takes path as a String argument, and converts it to an NSURL:

    
    
    func get(path: String) {
        let url = NSURL(string: path)
        ...
    

    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.

    
    func get(path: String) {
        let url = NSURL(string: path)
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
            println("Task completed")
            if(error) {
                // If there is an error in the web request, print it to the console
                println(error.localizedDescription)
            }
            var err: NSError?
            var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
            if(err?) {
                // If there is an error parsing JSON, print it to the console
                println("JSON Error (err!.localizedDescription)")
            }
            var results = jsonResult["results"] as NSArray
            // Now send the JSON result to our delegate object
            self.delegate?.didReceiveAPIResults(jsonResult)
            })
        task.resume()
    }
    

    Now in our searchItunesFor function, we can simply call on our new get() function and slim it down to the bare essentials:

    
    func searchItunesFor(searchTerm: String) {
        // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
    
        // Now escape anything else that isn't URL-friendly
        let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
        let urlPath = "https://itunes.apple.com/search?term=(escapedSearchTerm)&media=music&entity=album"
        get(urlPath)
    }
    

    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’s no reason not to just break the get() part out in to it’s own method.

    Now, we can quickly add a second API function to lookup a specific album. But first, let’s modify our album model to store a collectionId variable, used by iTunes to identify individual albums. In our Album class, add a new variable collectionId of type Int.

    
    var collectionId: Int?
    

    ..and modify the constructor to accept collectionId as an argument:

    
    
    init(name: String!, price: String!, thumbnailImageURL: String!, largeImageURL: String!, itemURL: String!, artistURL: String!, collectionId: Int?) {
    
    

    Finally, add a line to set the collectionId as one of our variables being passed in through init():

    
    
    self.collectionId = collectionId
    

    Great! We can now initialize Albums with a collectionId, but now our existing Album code is wrong, it’s missing the collectionId parameter. In the SearchResultsViewController, find the line that creates the newAlbum.

    
    var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL!, largeImageURL: imageURL!, itemURL: itemURL!, artistURL: artistURL!)
    

    Modify this to get the collectionId our of the result dictionary, and pass it in to the Album constructor:

    
    var collectionId = result["collectionId"] as? Int
    var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL!, largeImageURL: imageURL!, itemURL: itemURL!, artistURL: artistURL!, collectionId: collectionId!)
    

    Setting up the Details View

    In the previous tutorial we added a DetailsViewController to our storyboard. Let’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’re going to load in our list of tracks.

    Let’s now connect this new TableView to a property in DetailsViewController called tracksTableView.

    
    @IBOutlet var tracksTableView : UITableView
    

    Now, set the dataSource and delegate of the table view to the DetailsViewController, and implement the protocol as we did before:

    
    func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return 0
    }
    
    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        return nil
    }
    

    If we’re going to show tracks we’re going to need another model. Create a new Swift class file called ‘Track’ with no superclass, and give it three String properties for title, price, and previewUrl.

    
    import Foundation
    class Track {
    
        var title: String?
        var price: String?
        var previewUrl: String?
    
        init(dict: NSDictionary!) {
            self.title = dict["trackName"] as? String
            self.price = dict["trackPrice"] as? String
            self.previewUrl = dict["previewUrl"] as? String
        }
    }
    

    You may notice our initializer here takes an NSDictionary instead of individual arguments like our Album class. I decided to make this change to demonstrate how you might would cut down on parsing of JSON/Dictionary data in your controller code, and instead let the Model take care of it. This is generally a good practice because it limits the knowledge about the schema of this object to the model. For example, if the iTunes API changes then we can simply change this one function, instead of changing it every place we parsed the JSON for tracks.

    Now, in DetailsViewController, let’s add an array of tracks as a new property.

    
    var tracks: Track[] = []
    

    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.

    Let’s add a new function to APIController that takes an Int collectionId argument, and tell it to use get() to get track information

    
    func lookupAlbum(collectionId: Int) {
        get("https://itunes.apple.com/lookup?id=\(collectionId)&entity=song")
    }
    

    We’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.

    
    class DetailsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, APIControllerProtocol {
        @lazy var api: APIController = APIController(delegate: self)
    

    In the DetailsViewController viewDidLoad method, we want to add a portion to pull down tracks based on the selected album, so let’s add the following:

    
    // Load in tracks
    if self.album?.collectionId? {
        api.lookupAlbum(self.album!.collectionId!)
    }
    

    This is all stuff we’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. You may not recognize the self.album?.collectionId? part. This is called optional chaining in Swift, and it allows for a concise way to check that variables are present before attempting to use them. It’s the equivalent of saying the following:

    
    if self.album? {
        if self.album!.collectionId? {
            api.lookupAlbum(self.album!.collectionId!)
        }
    }
    

    It’s much cleaner though, and without the nested methods it’s easier to read. Thanks to iOS8 Swift!

    To fully adhere to our APIControllerProtocol, we need to implement the didReceiveAPIResults() function in this class too. We’ll use this to load in our track data. This API call returns the album before it returns the list of tracks, so we also add a check to make sure the “kind” key is set to “song”.

    
    func didReceiveAPIResults(results: NSDictionary) {
        if let allResults = results["results"] as? NSDictionary[] {
            for trackInfo in allResults {
                // Create the track
                if let kind = trackInfo["kind"] as? String {
                    if kind=="song" {
                        var track = Track(dict: trackInfo)
                        tracks.append(track)
                    }
                }
            }
        }
      dispatch_async(dispatch_get_main_queue(), {
        self.tracksTableView.reloadData()
      })
    }
    

    Now let’s modify the numberOfRowsInSection to be the track count

    
    func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return tracks.count
    }
    

    And let’s modify the cellForRowAtIndexPath method to load in our track data. First, we need to add a prototype cell to the TableView in our storyboard, as we did before. Set the Identifier to “TrackCell” this time in the Attributes Inspector (on right-hand panel while selecting the Table View.)

    Adding a Custom Table View Cell in Swift

    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.

    
    class TrackCell: UITableViewCell {
        @IBOutlet var playIcon: UILabel
        @IBOutlet var titleLabel: UILabel
    }
    

    Now in our storyboard, select the prototype cell within our DetailsViewController’s TableView, and change it’s class to ‘TrackCell’ under the Identity Inspector in the right-hand panel. Make sure you’ve selected a prototype cell. The easiest way to make sure you have the right object is to use the hierarchy on the left-hand side of the storyboard view, you may need to expand this to see everything by clicking the icon in the bottom-left hand corner of the window. As a way to double-check our work from before, click on the Attributes tab and check that ‘Identifier’ is also set to ‘TrackCell’. By doing this we have a reusable cell named TrackCell, which is of class type TrackCell, and it has two custom Label views.

    So now let’s drag two labels on to our prototype cell. Make one of them small and on the left, around 23×23 points, for a ‘Play/Stop’ 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->Special Characters and find a play button looking icon. As an optional challenge, try using an image for the button icon!

    When you’re done you should have a prototype cell looking something like this (note: this screenshot from Xcode 5, not 6, due to NDA)

    xcode 6 label prototype

    Now connect your two labels to your playIcon and titleLabel IBOutlets on the TrackCell by using the Connections Inspector (the last icon on the right-hand panel in Xcode)

    Finally, sometimes Storyboards have an issue where the labels don’t show up for a size class even when it is installed. So make sure your labels are installed for all size classes. Select the playIcon label and go in to the attributes inspector. At the bottom you should see some size classes with checkboxes. Make sure each one you need is checked. And make sure the one that just says ‘+ Installed’ is checked.

    In the DetailsViewController, we can now implement the custom cells by getting the TrackCell object and casting it to our class with ‘as TrackCell’

    
    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        var cell = tableView.dequeueReusableCellWithIdentifier("TrackCell") as TrackCell
    
        var track = tracks[indexPath.row]
        cell.titleLabel.text = track.title
        cell.playIcon.text = "YOUR_PLAY_ICON"
    
        return cell
    }
    

    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 “YOUR_PLAY_ICON” text should be replaced with the play icon, which again, you can get by hitting Edit->Special Characters in the Mac OS menu bar. Don’t forget to put quotes around it!

    Next we grab the track we need from our tracks array, just as before with albums.

    Finally we access our custom IBOutlet variable, titleLabel, set it’s text to be the track title, and do the same with playIcon.

    Okay, next we want to set up a way to actually hear some audio. We’re going to use the MPMoviePlayerController class to do this. It’s easy to work with, and works just fine with audio-only streams.

    First off, in our DetailsViewController class let’s add the mediaPlayer as a property, right under the class definition add:

    
    var mediaPlayer: MPMoviePlayerController = MPMoviePlayerController()
    

    ERROR! Use of undeclared type MPMoviePlayerController

    It’s okay, this is just because we need to add the framework, it isn’t included by default in our project.

    Just add the following to the top of your DetailsViewController:

    
    import MediaPlayer
    

    Next, let’s kick off the audio playing when a user selects one of the track’s rows. Add the following to our DetailsViewController:

    
    func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
        var track = tracks[indexPath.row]
        mediaPlayer.stop()
        mediaPlayer.contentURL = NSURL(string: track.previewUrl)
        mediaPlayer.play()
        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? TrackCell {
            cell.playIcon.text = "YOUR_STOP_ICON"
        }
    }
    

    The line mediaPlayer.stop() stop’s the currently playing track. If there isn’t one playing, nothing happens. We don’t want to play multiple tracks at once so let’s make sure we stop a track if another one is clicked.

    Next, mediaPlayer.contentURL sets a url for where the media player should load it’s content. In our case it’s from the url stored in track.previewUrl.

    Finally, we call mediaPlayer.play(), and get the track cell for the tapped row index.
    If this row is still visible, it’ll set ‘cell’ and here we can change the playIcon label to instead show the stopped icon, which we set again by using Edit->Special Characters on the Mac OS menu bar.

    If you run your app, you should now have a fully working iTunes music preview application! This by itself is pretty great, but let’s add one more thing to make it even more slick, some smooth table view cell animations.

    Adding Animations in Swift

    This is actually really easy, and has a very cool visual effect. All we’re going to do is add the following function to both our SearchResultsViewController, and our DetailsViewController:

    
    func tableView(tableView: UITableView!, willDisplayCell cell: UITableViewCell!, forRowAtIndexPath indexPath: NSIndexPath!) {
        cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)
        UIView.animateWithDuration(0.25, animations: {
            cell.layer.transform = CATransform3DMakeScale(1,1,1)
        })
    }
    

    This code has the same issue as before. CATransform3DMakeScale is undefined because we need QuartzCore as part of our project. This time though, we actually do have the library already as part of the project template, we just need to import it. So add the following to the top of your DetailsViewController and SearchResultsViewController files:

    
    import QuartzCore
    

    Now run the app and scroll around, neat right? So how’s it work?

    The 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.

    
    cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)
    

    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’ll know what this means right away. If not, it’s not super important. The point is, it makes things scale, and here we’re scaling it down to 10% by setting the x and y values to 0.1.

    So, we are basically just setting the cell layer’s transform to be 90% smaller.

    Next we set the cell layer’s transform to a new scale, this time of (1,1,1). This just means that it should return to it’s original scale. Because this line is run inside of the animateWithDuration() block, we get the animation for free courtesy of Core Animation.

    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. :)

    having issues?

    We have a Questions and Answer section where you can ask your iOS Development questions to thousands of iOS Developers.

    Ask Question

    FREE Download!

    Get your FREE Swift 2 Cheat Sheet and quick reference guide PDF download when you sign up to SwiftMonthly


    Sharing is caring

    If you enjoyed this tutorial, please help us and others by sharing using one of the social media buttons below.


    Written by:

    I am an app developer in Austin, TX. I spend time dabbling with iPhone, iPad, Android, and web technologies. I write about technology, startups, and my technology & entrepreneurial experiments. More of my Swift Tutorials can be found on my site.

    Comments

    comments