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.
Can you please include the actual project for comparison?
ReplyDeleteCan you please include the actual project for comparison?
ReplyDelete