Developing iOS Apps Using Swift Tutorial Part 2

This section completely updated to reflect changes in Xcode 6.3, as of April 16, 2015

In part 1 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some text inside of them. If you haven’t read that yet, give it a read here.

For this section, we’re going to do something a little more ambitious. We’re going to hit the iTunes Search API for the iTunes Store, download the JSON results, parse them in to Dictionaries and then populate our Table View with this information. Then, we will add some user interaction by adding a click event to the tableview, so that when an item is clicked the iTunes store item will be opened.

If this sounds like a lot of work, don’t sweat it. This is pretty basic functionality for iOS apps and it’s one of the most common things any developer has to do. Let’s get going…


Connecting the UI

The first thing we need to do is get a reference to our tableView, so it can be used from code. Go ahead and add this line to your ViewController.swift file, just under the class definition, but outside of any functions.

@IBOutlet var appsTableView : UITableView!

This bit of code allows up to connect our Table View in our Storyboard to this variable, “appsTableView”. Save this file and open up your storyboard. Now select the View Controller object (the one with a yellow icon) and in the right-hand side pane click the last tab, the Connections Inspector. Here you should now see an outlet for “appsTableView”. Click and drag from the dot next to this outlet on to the Table View in our scene.

Let’s also add a variable to hold the table data itself. Just under the class definition for ViewController add:

var tableData = []

Making the API Request

Now that we have the UI connected, we’re ready to make an API call. Create a new function called searchItunesFor(searchTerm: String). We’ll use this to make our requests happen for arbitrary search terms.

To keep this tutorial short, I’m going to just post my final code and let the comments do some of the explaining. I’m always open to questions and further discussion in the comments though, so feel free to chime in!

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
    if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
        let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
        let url = NSURL(string: urlPath)
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
            println("Task completed")
            if(error != nil) {
                // If there is an error in the web request, print it to the console
                println(error.localizedDescription)
            }
            var err: NSError?
            if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
                if(err != nil) {
                    // If there is an error parsing JSON, print it to the console
                    println("JSON Error \(err!.localizedDescription)")
                }
                if let results: NSArray = jsonResult["results"] as? NSArray {
                    dispatch_async(dispatch_get_main_queue(), {
                        self.tableData = results
                        self.appsTableView!.reloadData()
                    })
                }
            }
        })
        
        // The task is just an object with all these properties set
        // In order to actually make the web request, we need to "resume"
        task.resume()
    }
}

Let’s go line-by-line.

First, we need to do some fixing of the search terms we pass in, the Search API wants terms in the form of “First+Second+Third+Words” rather than “First%20Second%20…” etc. So instead of URL-encoding, we use an NSString method called stringByReplacingOccurencesOfString. This returns a modified versions of the searchTerm variable with all our spaces replaced with + symbols.

Next, we actually escape the search term in case there are other symbols that won’t fit in a URL by using the function stringByAddingPercentEscapesUsingEncoding.

The next 2 lines define an NSURL object that can be used as a Request URL for iOS’s networking API.

These next two lines are what gets us going with a NSURLSession and defines the task we want it to perform. This is were the heavy lifting begins, as the dataTaskWithURL method takes a closure as it’s final argument, which is executed after a request is sent, and a result is determined.

let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in

The first line grabs the default NSURLSession object. This is used for all our networking calls. The second line then creates the connection task which is going to be used to actually send the request. dataTaskWithURL has a closure as it’s last parameter, which gets run upon completion of the request. Here we check for errors in the response, then parse the JSON, and call the delegate method didReceiveAPIResults.

Finally, task.resume() actually begins the request.

Because this task happens in the background, we need to jump in to the foreground before we update the UI. So we need to use dispatch_async to move back in to the main thread, and reload the table view. The first argument specifies the thread the update should perform on, which we specify as dispatch_get_main_queue(). This is a built-in method that simply returns the main thread (the UI thread).

Making the API call

Now we’ve got a method that starts an iTunes search response when we call it. So let’s insert the following at the end of viewDidLoad…

searchItunesFor("JQ Software")

This will find any software products on the iTunes store containing that phrase, which in this case will include a couple of games and various apps.

Receiving the response

Finally our request code is done and all data has been received, didReceiveAPIResults is called and we’re ready to use the data in our app. Hooray!

The closure method here uses the NSJSONSerialization class to convert our raw data in to useful Dictionary objects by deserializing the results from iTunes.

We can now set our self.tableData object to be the resulting data, and tell the appsTableView to reload it’s content. This will cause the Table View object to run it’s own delegate methods. Defining this is the final step in this part of the tutorial.

Updating the Table View UI

You may remember from last time we implemented the function for our Table View that we had a count method, which determines the number of rows; and a cell method, which actually creates the cell and modifies it for each row.

We’re going to update these now to use the data we pulled down from the web.

Swap out your methods with these two functions:

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

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")
    
    if let rowData: NSDictionary = self.tableData[indexPath.row] as? NSDictionary,
        // Grab the artworkUrl60 key to get an image URL for the app's thumbnail
        urlString = rowData["artworkUrl60"] as? String,
        // Create an NSURL instance from the String URL we get from the API
        imgURL = NSURL(string: urlString),
        // Get the formatted price string for display in the subtitle
        formattedPrice = rowData["formattedPrice"] as? String,
        // Download an NSData representation of the image at the URL
        imgData = NSData(contentsOfURL: imgURL),
        // Get the track name
        trackName = rowData["trackName"] as? String {
            // Get the formatted price string for display in the subtitle
            cell.detailTextLabel?.text = formattedPrice
            // Update the imageView cell to use the downloaded image data
            cell.imageView?.image = UIImage(data: imgData)
            // Update the textLabel text to use the trackName from the API
            cell.textLabel?.text = trackName
    }
    return cell
}

The numberOfRowsInSection is now simply returning the number of resultant objects from the tableData member, which is set in our prior connectionDidFinishLoading method.

The cellForRowAtIndexPath is also not changed dramatically in this case. Rather than simply returning the row number, we use the row number to grab three pieces of information: the track name, the artwork url, and the price.

Using these keys we construct the title, subtitle, and an image to go along with the cell.

If you’re coming from another language, you may notice the syntax is a little unusual here. The syntax goes something like this:

if let variableA = optionalThing as? Type,
    variableB = anotherOptionalThing as? Type {
        // variableA and variableB are confirmed to exist
}

This is part of optional binding. The statement “if let variableA = optionalThing as? Type {}” will only execute the code within {} if optionalThing is able to be casted to Type. It’s a check.. can we assign a variable called variableA to optionalThing as a type of Type? If so, then execute this next bit of code. You can also specify multiple checks, in this case we check if both variableA and variableB are able to be assigned, if so then we execute the code in the block.

We use optional binding in our cellForRowAtIndexPath method in order to make sure artworkUrl60, formattedPrice, and trackName are actually keys we get back from the API. If any of these fail, the code within the curly braces is skipped, preventing an error from occurring when we try to set the text or image to a nil value.

 

Try running our app, and you’ll see we for the first time have created something that actually looks like an app all in Swift!

But, why is it so laggy!?
If you scroll around you might notice it lags when showing table view cells.
In this tableview, we’re not properly handling quite a few things. Over the next 3 sections we’ll dive in to what that means and what the proper changes we need to make are. If you like, jump ahead to part 5 where these issues are resolved.

In my upcoming book, I delve more deeply in to why this happens. Learn more about it here.

Next time in Part 3 we’ll work on the interaction piece of the app, allowing users to search for anything they like, and making the table cells clickable!

The completed source code to this section is available here.

Go to Part 3 Now ->


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS