Sunday, June 22, 2014

Access REST Web Service with Apple’s new Swift Language - Part 2

In my last post, I showed how to use Apple’s new Swift programming language to access Apple’s Restful iTunes Search API to retrieve a list of songs by Jimmy Buffett.  In the post we simply logged the JSON results to the console.  In this post we will expand on that example to display the results, with album covers, in a UITableView.  We have two requirements for loading images, they must be loaded in the background so the UI does not freeze while the images load and to create an image cache so we do not reload images we already have.

If you have not gone though my last post, you will want to get the code at the end of that post because we will be using the same ITunesSearchAPI class here.

Lets start off by adding the UITableViewDataSource and the UTTableViewDelegate to our ViewController.  The ViewController definition will look like this:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, ITunesSearchAPIProtocol
{

}

Now lets define the IBOutlet for our UITableView, the NSArray to hold our table data, the NSMutableDictionary that will be our image cache and the ITunesSearchAPI object (the code for the ITunesSearchAPI can be found in my last post).  Add the following code to the ViewController class:

    @IBOutlet var appsTableView : UITableView
    var api: ITunesSearchAPI = ITunesSearchAPI()
    var tableData: NSArray = NSArray()
    var imageCache = NSMutableDictionary()

 Lets save the ViewController class and go to the Storyboard.  In the view for the ViewController add a UITableView and then set the dataSource and delegate like you did with Objective-C.




Now lets go back to the ViewController class.  There are no changes to the viewDidLoad() function from my previous post.  The code should look like this:

    override func viewDidLoad() {
        super.viewDidLoad()       
        api.delegate = self;
        api.searchItunesFor("Jimmy Buffett")
    }

We need to make a change to the didReceiveResponse function so our tableview reloads once the data is received from the iTunes search API.  The new didReceiveResponse looks like this:

    func didRecieveResponse(results: NSDictionary) {
        // Store the results in our table data array
        println("Received results")
        if results.count>0 {
            self.tableData = results["results"] as NSArray
            self.appsTableView.reloadData()
        }

    }

Lets implement the delegate functions for the UITableView.  For this example we will be implementing the following three functions:

·       func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int
·       func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
·       func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!)

If you are familiar with using a UITableView in Objective-C, then these functions should look very familiar.  The first function returns the number of rows for the table view.  The second function returns a UITableViewCell to render for the given row.  The third function is called when a row is selected.  Now lets implement these functions.

The first UITableView delegate function simply returns the number of rows for the table view so this function will return the number of objects in our tableData NSArray object.

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

The second UITableView delegate function is a bit more complicated.  Lets look at the code first and then I will walk though it.

    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath:
                NSIndexPath!) -> UITableViewCell! {
        let kCellIdentifier: String = "MyCell"
       
        //the tablecell is optional to see if we can reuse cell
        var cell : UITableViewCell?
        cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as?
               UITableViewCell
       
        //If we did not get a reuseable cell, then create a new one
        if !cell? {
            cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:
                     kCellIdentifier)
        }

        //Get our data row
        var rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary
       
        //Set the track name
        let cellText: String? = rowData["trackName"] as? String
        cell!.text = cellText
        // Get the track censored name
        var trackCensorName: NSString = rowData["trackCensoredName"] as NSString
        cell!.detailTextLabel.text = trackCensorName

        cell!.image = UIImage(named: "loading")
     
       
        dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
           
            // Grab the artworkUrl60 key to get an image URL
            var urlString: NSString = rowData["artworkUrl60"] as NSString
           
            // Check the image cache for the key (using the image URL as key)
            var image: UIImage? = self.imageCache.valueForKey(urlString) as? UIImage
           
            if( !image? ) {
                // If the image does not exist in the cache then we need to download it
                var imgURL: NSURL = NSURL(string: urlString)
               
                //Get the image from the URL
                var request: NSURLRequest = NSURLRequest(URL: imgURL)
                var urlConnection: NSURLConnection = NSURLConnection(request: request,
                         delegate: self)

                NSURLConnection.sendAsynchronousRequest(request, queue:
                          NSOperationQueue.mainQueue(), completionHandler: {(response:
                          NSURLResponse!,data: NSData!,error: NSError!) -> Void in

                    if !error? {
                        image = UIImage(data: data)
                       
                        // Store the image in the cache
                        self.imageCache.setValue(image, forKey: urlString)
                        cell!.image = image
                        tableView.reloadData()
                    }
                    else {
                        println("Error: \(error.localizedDescription)")
                    }
                    })
               
            }
            else {
                cell!.image = image
            }
           
           
            })
       
       
        return cell
       
    }

We start this function by getting a reusable cell.  If we are unable to reuse a cell we create a new one.  This is pretty standard for a UITableView because reusing cells cuts down on memory usage when we have table views that contain large number of rows.

After we have our cell, we retrieve the data from the tableData NSArray object.  We do this by getting the row from the NSIndexPath object that was passed into this function.  Once we have the data we are able to populate the cell.   We retrieve the track name and also the track censor name and display them in the cell’s text and detailTextLabel properties.

We set the cell’s image object to an image named loading.png.   This image is a temporary image that shows the image is loading in the background.  This is what the image looks like (empty box with the word "Loading"):



We now want to load the image of the album cover in the background so our UI does not freeze.  We do this by using the dispatch_async function from GCD to submit a block for asynchronous execution on a dispatch queue.  This block contains the code to retrieve the image from iTunes search API.

We begin the block by getting the URL for the album image.  We then try to retrieve the image from our image cache.  The line: if (!image ?)  Reads “if the image is nil”.  If the image is nil, we need to download it from the iTunes search API.

To download the image from the iTunes search API, we first create an NSURL object and use it to create an NSURLRequest.  We then use the NSURLRequest to create an NSURLConnection object and call the sendAsynchronousRequest function to download the image.

In the completion handler of the of the sendAsynchronousRequest function, we pass a block of code that handles the image when it is received.  This block begins by checking for any errors.  If there were no errors, we create an UIImage object from the data that was received, store it in the image cache and add it to the cell.

The third UITableView delegate function will display a UIAlertView with the track’s censored name and the release date of the track that was selected.  Let’s look at the code for this function.

    func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
          //get the selected tracks information
        var rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary

        var name: String = rowData["trackCensoredName"] as String
        var releaseDate: String = rowData["releaseDate"] as String
       
          //Show the alert view with the tracks information
        var alert: UIAlertView = UIAlertView()
        alert.title = name
        alert.message = releaseDate
        alert.addButtonWithTitle("Ok")
        alert.show()
    }

This function retrieves the track information and then pops up a UIAlertView displaying the track’s censored name and the release date.


If you have suggestions about other topics that you would like to see covered here, please leave a comment. 

2 comments:

  1. Can you please include the actual project for comparison?

    ReplyDelete
  2. Can you please include the actual project for comparison?

    ReplyDelete