Show Menu

Looking for an iOS Developer?

Submit your 30 day Job Listing for FREE

This article is part of the create iOS8 Applications with Swift tutorial series, here are the other published articles:

    Slimming down our API Controller code

    First off, our original plan was to show iTunes music information. So let’s modify the API controller to better handle this information. Before we start making changes I want to simplify the class. We’re going to use the sendAsynchronousRequest we learned about in part 5, and slim down our API controller. To do that, let’s remove the following functions:

    
    func connection(connection: NSURLConnection!, didFailWithError error: NSError!)
    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
    func connectionDidFinishLoading(connection: NSURLConnection!) {
    

    Also we won’t be needing our mutable data object any more, so let’s remove that too:

    
    var data: NSMutableData = NSMutableData()
    

    Finally, in our searchItunesFor() function, let’s remove the part that creates and sends the request

    
    var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
    connection.start()
    

    And replace that with the following:

    
    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
        if error? {
            println("ERROR: (error.localizedDescription)")
        }
        else {
            var error: NSError?
            let jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
            // Now send the JSON result to our delegate object
            if error? {
                println("HTTP Error: (error?.localizedDescription)")
            }
            else {
                println("Results recieved")
                self.delegate?.didRecieveAPIResults(jsonResult)
            }
        }
    })
    

    What we’ve done here is removed our Protocol-Delegate functions, and instead opted to use the more functional API, sendAsynchronousRequest. What’s happening here is the function is sending our ‘request’ object as a parameter to sendAsynchronousRequest, on the main queue, with a completion handler which is another function. When the request is done it calls our inline function, which checks for an error and logs it to the console if there is one. If there is no error, it then converts the result in to a JSON NSDictionary, and passes it back to our delegate after checking if the JSON was parsed successfully. That’s quite a bit of replaced code, and it’s the kind of thing we have to look forward to when working with the newer APIs designed for a functional approach to iOS development.

    In a part 3 Part 3: Developing iOS8 Apps Using Swift – Best Practices when we created this class, we inherited from NSObject, but this isn’t necessary. So modify the class definition to look like this:

    
    class APIController {
    

    What this does change however, is now we don’t have an init method! So let’s add one, and let’s make sure it includes the delegate. An API Controller without a delegate isn’t all that useful, so why even offer the option to make one?

    
    init(delegate: APIControllerProtocol?) {
        self.delegate = delegate
    }
    

    Now in our SearchResultsController, we need to change a few things. First of all we need to make the api an optional, because we can’t pass self in the initializer. Where we have our declaration of ‘api’ now we need to swap out for this:

    
    var api: APIController?
    

    This says we have an object called api who is of type APIController, but it may be nil.

    Then, in viewDidLoad we need to instantiate the api.

    
    self.api = APIController(delegate: self)
    

    There’s no longer any need to set the delegate on a separate line, our init method now handles that automatically.

    Creating a Swift model for iTunes Albums

    Let’s also modify our call to the searchItunesFor() method to use a search term for music, and adjust it so that it forces unwrapping of the api object by adding a ! to the end. We do this because we just created api and we know it exists. We’ll also show a networkActivityIndicator, to tell the user a network operation is happening. This will show up on the top status bar of the phone.

    
    UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    self.api!.searchItunesFor("Bob Dylan");
    

    Now in our urlPath in the APIController, let’s modify the API parameters to look specifically for albums.

    
    var urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album"
    

    Great! Now we have a much cleaner API Controller with a lot less code!

    We need a model!

    In order to facilitate passing around information about albums, we should create a model of what an album is exactly. Create a new swift file and call it Album.swift with the following contents:

    
    class Album {
        var title: String?
        var price: String?
        var thumbnailImageURL: String?
        var largeImageURL: String?
        var itemURL: String?
        var artistURL: String?
    
        init(name: String!, price: String!, thumbnailImageURL: String!, largeImageURL: String!, itemURL: String!, artistURL: String!) {
            self.title = name
            self.price = price
            self.thumbnailImageURL = thumbnailImageURL
            self.largeImageURL = largeImageURL
            self.itemURL = itemURL
            self.artistURL = artistURL
        }
    }
    

    It’s a pretty simple class, it just holds a few properties about albums for us. We create the 6 different properties as optional strings, and add an initializer that requires each on be unwrapped before use. The initializer is very simple, it just sets all the properties based on our parameters.

    So now we have a class for album objects, let’s use it!

    Using our new Swift Album model

    Back in our SearchResultsController, let’s remove the tableData NSArray variable, and instead opt for a Swift native array for Albums. In swift, this is as easy as:

    
    var albums: Album[] = []
    

    This creates an empty array containing exclusively Albums. We’ll now need to change our tableView dataSource and delegate methods to understand albums.
    In the numberOfRowsInSection method, let’s change the number of items to the count of albums in our albums array:

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

    Now in cellForRowAtIndexPath, let’s swap out those dictionary lookups for a single album lookup:

    
    let album = self.albums[indexPath.row]
    cell.text = album.title
    cell.image = UIImage(named: "Blank52")
    cell.detailTextLabel.text = album.price
    

    And a little later where we grab the thumbnail, swap out the urlString setter with this:

    
    let urlString = album.thumbnailImageURL
    

    Some of you may have noticed that our image sometimes are buggy, and overwrite each other. This is happening due to cell re-use. I’ve modified the code to check for the existence of the cell in question (which implies it’s visibility) which can be seen within our sendAsynchronousRequest function. It’s a big block of code so here is a gist with the swift code in.

    Creating Album objects from JSON

    Now, all of this is not much use if we aren’t creating our album information in the first place. We need to modify our didRecieveAPIResults method to take album results, create them from the JSON response, and save them in to the albums array. The final method should look like this:

    
    func didRecieveAPIResults(results: NSDictionary) {
        // Store the results in our table data array
        if results.count>0 {
            
            let allResults: NSDictionary[] = results["results"] as NSDictionary[]
    
            // Sometimes iTunes returns a collection, not a track, so we check both for the 'name'
            for result: NSDictionary in allResults {
                
                var name: String? = result["trackName"] as? String
                if !name? {
                    name = result["collectionName"] as? String
                }
                
                // Sometimes price comes in as formattedPrice, sometimes as collectionPrice.. and sometimes it's a float instead of a string. Hooray!
                var price: String? = result["formattedPrice"] as? String
                if !price? {
                    price = result["collectionPrice"] as? String
                    if !price? {
                        var priceFloat: Float? = result["collectionPrice"] as? Float
                        var nf: NSNumberFormatter = NSNumberFormatter()
                        nf.maximumFractionDigits = 2;
                        if priceFloat? {
                            price = "$"+nf.stringFromNumber(priceFloat)
                        }
                    }
                }
                
                let thumbnailURL: String? = result["artworkUrl60"] as? String
                let imageURL: String? = result["artworkUrl100"] as? String
                let artistURL: String? = result["artistViewUrl"] as? String
                
                var itemURL: String? = result["collectionViewUrl"] as? String
                if !itemURL? {
                    itemURL = result["trackViewUrl"] as? String
                }
                
                var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL!, largeImageURL: imageURL!, itemURL: itemURL!, artistURL: artistURL!)
                albums.append(newAlbum)
            }
            
            
            self.appsTableView.reloadData()
            UIApplication.sharedApplication().networkActivityIndicatorVisible = false
        }
    }
    

    This may look like a lot of new code, but what’s happening here is actually very simple. First, we’re grabbing an NSDictionary of the results key from the API results, which contains all our albums info.

    
    let allResults: NSDictionary[] = results["results"] as NSDictionary[]
    

    Then, we loop through every nested dictionary in allResults, assigning each element to a temporary variable named result by using the iOS8 Swift’s for-each syntax:

    
    for result: NSDictionary in allResults {
    
    

    Next you’ll see lots of this happening:

    
    var name: String? = result["trackName"] as? String
    if !name? {
        name = result["collectionName"] as? String
    }
    
    

    What’s happening here is that iTunes is using a different key for tracks vs albums. So all these fields should be declared as optionals, and we should check for both so at least one of them gets saved. This may be important for your app if you opted to use a different media type.

    Finally after formatting all our information and validating that it all exists, we create our new album, and add it to our albums array:

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

    Now that that’s taken care of, we reload our table view and turn off the network activity indicator

    
    self.appsTableView.reloadData()
    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    

    Creating a second view

    Now to actually show the details of an album, we’ll need a new view. First let’s create the class. Add a new file called DetailsViewController.swift that inherits from UIViewController. Our view controller will be pretty simple to start. We’re just going to add an album, and implement UIViewController’s init method as well as viewDidLoad().

    
    import UIKit
    
    class DetailsViewController: UIViewController {
    
        var album: Album?
    
        init(coder aDecoder: NSCoder!) {
            super.init(coder: aDecoder)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    }
    

    This code doesn’t do much yet, but that’s okay. We just need the class to exist in order to set up our storyboard.

    Since we’ll be pushing views back and forth on the stack we’ll want a navigation bar as well. It’s hard to explain in text, and there is an NDA preventing me from showing Xcode 6 screenshots, so instead I created a short video demonstrating how to do this in Xcode 5. The process is nearly identical for Xcode 6 Beta, and is not under any sort of NDA.

    In the video we did the following:

    • Embedded our view controller in a navigation controller using the Xcode shortcut in the Editor menu, by clicking the view controller, then selecting Editor->Embed In->Navigation Controller
    • Added a new view controller
    • Set it’s class and storyboard ID to ‘DetailsViewController’
    • Control+Clicked+Dragged from the table view cell in our first view controller to the new view controller we just created, and selected ‘push’ for the type of segue.
    • What this last step does is creates a segue on our navigation controller that pushes the new view on top of the stack. If you run the app now and click a cell, you should see this new view animate in.

    Let’s build out a simple UI for this new view. It’ll contain a UIImageView that is 100×100 pixels, a title Label, a button, and a text view. Drag all of these objects out of the object library and arrange them any way you like on the new view. Please note that before you do this, in Xcode 6 there is a concept of size classes. For our purposes, we just need to set the size class to iPhone. At the bottom of the window you’ll see something that might say something like ‘wAny hAny’. This is the current size class for the storyboard. If you’ve been wondering why your views are square, this is why. Click this and change it to represent iPhone portrait by setting it to ‘Compact Width | Any Height’.

    Providing the new view with Album information

    When the storyboard segue fires off, it first calls a function on whatever view controller is currently on the screen called prepareForSegue. We’re going to intercept this call in order to tell our new view controller which album we’re looking at. Add the following in SearchResultsViewController:

    
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject) {
        var detailsViewController: DetailsViewController = segue.destinationViewController as DetailsViewController
        var albumIndex = appsTableView.indexPathForSelectedRow().row
        var selectedAlbum = self.albums[albumIndex]
        detailsViewController.album = selectedAlbum
    }
    

    What’s happening here is the segue parameter being passed in has a member called destinationViewController, which is our fancy new DetailsViewController we just created. In order to set the album member on it, we first need to cast it to DetailsViewController using the ‘as’ keyword as shown above.

    Then, by using the indexPathForSelectedRow() method of our table view we can determine which album is selected at the moment this segue happens. Using this information, well tell our detailsViewController which album was clicked before it is displayed.

    Now I’m going to show you a pretty nifty feature of Xcode. We’re going to let it write some code for us.

    Open up your storyboard again let’s start creating IBOutlets for our image view, label, button, and text view. On the top-right hand corner of Xcode there is the ‘assistant’ button. The icon looks like a bowtie and suit jacket. Clicking on this will open up a code window right next to your storyboard window. Make sure that one of the panels is showing DetailsViewController.swift, and the other is showing Main.storyboard.

    Now, hold control, and click+drag from your image view to your code file. Just a line under your class definition for DetailsViewController. It’ll prompt you for a name, so let’s call it ‘albumCover’. The default options should be fine here. After doing this you should see this line of code newly added:

    
    @IBOutlet var albumCover : UIImageView
    

    We just created a new IBOutlet, and now it’s connected to our storyboard’s DetailsViewController. How cool is that?

    Do the same thing for the rest of the objects you added to your view. Next, let’s modify viewDidLoad so that it will load in the info we’re being passed to our view objects, here’s the final DetailsViewController code:

    
    import UIKit
    
    class DetailsViewController: UIViewController {
        
        @IBOutlet var albumCover : UIImageView
        @IBOutlet var titleLabel : UILabel
        @IBOutlet var detailsTextView : UITextView
        @IBOutlet var openButton : UIButton
        
        var album: Album?
        
        init(coder aDecoder: NSCoder!) {
            super.init(coder: aDecoder)
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            titleLabel.text = self.album?.title
            albumCover.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album?.largeImageURL)))
        }
    
    }
    

    The @IBOutlets are the UI connections made by our storyboards, and our viewDidLoad method sets the title and album cover variables to load in from our Album object.

    Now try running the app and taking a look. We can now drill in to details for albums and get a nice big detail view with the album cover and title. Because we pushed in a navigation controller, we also get a functional Back button for free!

    If you made it this far, I want to personally congratulate you so let me know on twitter (@jquave) that you pulled it off! You are well on your way to creating real iOS applications with Swift.

    What's Next....

    In the next part, we set up a full Detail view with a working music player, and implement some great animations. Stay tuned!

    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