RSNetworking is a networking library that I am writing in
the Swift programming language. I have
written a couple posts that show the features of RSNetworking and how to use
them but I have not shown how to use RSNetworking in a complete project. This post will walk you though creating an
app that will search iTunes using Apple's search API and display the results in a
UITableView using the new RSNetworking library. The GitHub repository for RSNetworking is located here: https://github.com/hoffmanjon/RSNetworking
Lets begin by creating a Single-View application project,
using Swift as the programming language, with XCode Beta 4. Once the project is created, copy the
RSNetworking files to the project. This
will include the follow files:
RSTransaction.swift
RSTransactionRequest.swift
RSURLRequest.swift
RSUtilities.swift
UIImageView+RSNetworking.swift
Notice that I did not include the RSNetworking.swift
file. This file will be removed from the
library in the very near future.
Now lets add a UITextField,
UIButton and a UITableView to the View Controller Scene. The scene should look like this in the
storyboard.
Now lets go to our ViewController.swift file. The first thing we need to do is to add some
outlets that will be used to connect our code to the UI elements. We will need three outlets one each for the UITextField, UIButton and UITableView. We define the outlets like this (you can see
the complete ViewController.swift class at the end of this post):
@IBOutlet var searchButton : UIButton!
@IBOutlet var searchTextField
: UITextField!
@IBOutlet var resultsTableView
: UITableView!
After these are added to the class, we will need to connect
them to the UI elements in the storyboard.
Next we will need to define an NSArray
that will hold the data for our UITableView. We will be using an NSArray instead of a Swift Array
because the array will contain various objects like a Strings, Arrays or
Dictionaries. We would need to define
the Swift array like this [Dictionary<String, Object>] = [] but
this is not allowed in Swift. In Swift
you need to define the type for both the key and value in a dictionary. The NSArray
is defined like this:
var tableData: NSArray = NSArray()
We also want to initialize an instance of the RSTransationRequest
class that will be used to make our requests.
The RSTransationRequest
class is used to make the requests to the server and return the results in the
format we request. We initialize it like
this:
var rsRequest: RSTransactionRequest = RSTransactionRequest()
The RSTransactionRequest exposes four
functions. These are:
* dataFromRSTransaction(transaction: RSTransaction, completionHandler
handler: RSNetworking.dataFromRSTransactionCompletionCompletionClosure):
Retrieves an NSData object from the
service defined by the RSTransaction. This is the main function and is used by the
other three functions to retrieve an NSData object prior to converting it to the
required format.
* stringFromRSTransaction(transaction: RSTransaction, completionHandler
handler:
RSNetworking.stringFromRSTransactionCompletionCompletionClosure):
Retrieves an NSString object from the
service defined by the RSTransaction. This function uses the dataFromRSTransaction
function to retrieve an NSData object and then converts it to an NSString
object.
*dictionaryFromRSTransaction(transaction: RSTransaction,
completionHandler handler:
RSNetworking.dictionaryFromRSTransactionCompletionCompletionClosure):
Retrieves an NSDictionary object from the
service defined by the RSTransaction. This function uses the dataFromRSTransaction
function to retrieve an NSData object and then converts it to an NSDictionary
object. The data returned from the URL
should be in JSON format for this function to work properly.
*imageFromRSTransaction(transaction: RSTransaction, completionHandler
handler:
RSNetworking.imageFromRSTransactionCompletionCompletionClosure):
Retrieves an UIImage object from the
service defined by the RSTransaction. This function uses the dataFromRSTransaction
function to retrieve an NSData object and then converts it to an UIImage
object.
We also want to initialize an instance of the RSTransaction
class. The RSTransaction class is
designed to contain everything needed to create a request to a HTTP web
service. We initialize it like this:
var rsTransGet: RSTransaction = RSTransaction(transactionType:
RSTransactionType.GET, baseURL: "https://itunes.apple.com", path: "search", parameters: ["term":"Jimmy+Buffett","media":"music"])
The RSTransaction class exposes four
properties, one initiator and one method.
These are:
- Properties
* TransactionType - This defines the HTTP request method. Currently there are three types, GET, POST,
UNKNOWN. Only the GET and POST actually
sends a request.
* baseURL - This is the base URL to use for the request. This will normally look something like
this: "https://itunes.apple.com". If you are going to a non-standard port you
would put that here as well. It would
look something like this: "http://mytestserver:8080"
*path - The path that will be added to the base url. This will normally be something like this:
"search". It can also include a longer path string
like: "path/to/my/service"
* parameters - Any parameters to send to the service.
- Initiators
* init(transactionType: RSTransactionType, baseURL: String, path: String, parameters: [String: String])
- This will initialize the RSTransaction with all properties needed.
- Function
* getFullURLString() -> String - Builds and returns the full URL needed
to connect to the service.
Now we need to override the ViewController’s viewDidAppear()
function. This function is called after
the view is displayed but is not part of the standard template so it is not
created when XCode creates the UIViewController class. You can override the function like this:
override func
viewDidAppear(animated: Bool) {
}
Since we are writing this app to run on an iPhone/iPod/iPad we
cannot guarantee that it will always be connected to the Internet. Therefore, the first thing we need to do is
to check to see if itunes.apple.com
is reachable. We can do this with the isHostnameReachable()
function in the RSUtilities
class. We would add this check to the viewDidAppear
function like this:
override func
viewDidAppear(animated: Bool) {
//check to see if host is reachable
if (!RSUtilities.isHostnameReachable("www.apple.com")) {
//If host is not reachable, display a
UIAlertController informing the user
var alert = UIAlertController(title: "Alert", message: "You are
not conected to the Internet", preferredStyle: UIAlertControllerStyle.Alert)
//Add alert action
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
//Present alert
self.presentViewController(alert,
animated: true, completion: nil)
}
}
If itunes.apple.com is not reachable, we display an alert
to the user letting them know.
Now lets add the function that will be called when the
search button is pressed. It will look
like this:
@IBAction func search(AnyObject) {
}
Don’t forget to go back into the Storyboard and connect the
button to this function.
The search() function will need to retrieve the
text from the searchTextField UITextField,
convert that text to a useable string (replacing whitespace with a + symbol)
and then make the request to itunes.apple.com.
The full search function looks like this:
@IBAction func search(AnyObject) {
//Get text for search
var text: String = searchTextField.text;
//Convert spaces to "+"
let searchText = text.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch,
range: nil)
//Set the parameters for the
RSTransaction object
rsTransGet.parameters = ["term":searchText,"media":"music"]
//Send request
rsRequest.dictionaryFromRSTransaction(rsTransGet,
completionHandler: {(response : NSURLResponse!,
responseDictionary: NSDictionary!, error: NSError!) -> Void in
if !error? {
//If there was no error
//Set the tableData NSArray to the
results that were returned from the iTunes search and reload the table
self.tableData =
responseDictionary["results"] as NSArray
//Reload the
UITableView
self.resultsTableView.reloadData()
} else {
//If there was an error, log it
println("Error : \(error)")
}
})
}
After we retrieve the text from the searchTextField UITextField and convert that text to a
useable string, we use that new String to set the parameters of our RSTransaction
object. We then submit the RSTransaction
using the dictionaryFromRSTransaction
method of the RSTransactionRequest object, this will create and send the
request to Apple’s iTunes search API.
We pass a block of code to the dictionaryFromRSTransaction
method that is run when a response is received.
If we did not receive an error, we populate the tableData NSArray
object from the response and then reload the UITableView to display the
new results.
The only thing left to do is to implement the UITableViewDelegate
methods. The first method we will
implement simply returns the number of rows in the tableData NSArray:
func
tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
The second UITableViewDelegate that we implement will
create the UITableViewCells.
func
tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let kCellIdentifier: String = "MyCell"
//tablecell 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 the data from the NSArray
var rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary
//Set the text of the cell
cell!.textLabel.text = rowData["trackName"] as? String
//Set the
detailText of the cell
cell!.detailTextLabel.text = rowData["trackCensoredName"] as NSString
//Use the setImageForURL method added to the UIImageView by the
//RSNetworking catagory to load an image from a URL.
//While the image loads we use a placeholder image
var imageURL: NSString = rowData["artworkUrl60"] as NSString
var mCell = cell
mCell!.imageView.setImageForURL(imageURL, placeHolder: UIImage(named: "loading"))
return cell
}
The implementation of this method is similar to the
Objective-C implementation. The
interesting part is at the end where we use the setImageForURL method that is
added to the UIImageView
by RSNetworking’s UIImageView+RSNetworking category. This category will add a placeholder image
that is used while the real image downloads.
I added this project to RSNetworking’s GitHub repository so
you can view the project as a whole. The
name of the project is iTunesSearch.
The complete ViewController class looks like this:
import UIKit
class ViewController:
UIViewController {
@IBOutlet var searchButton : UIButton!
@IBOutlet var searchTextField
: UITextField!
@IBOutlet var
resultsTableView : UITableView!
var tableData: NSArray = NSArray()
var rsRequest: RSTransactionRequest = RSTransactionRequest()
var rsTransGet: RSTransaction = RSTransaction(transactionType:
RSTransactionType.GET, baseURL: "https://itunes.apple.com", path: "search", parameters: ["term":"Jimmy+Buffett","media":"music"])
override func viewDidLoad() {
super.viewDidLoad()
}
override func
viewDidAppear(animated: Bool) {
//check to see if host is reachable
if (!RSUtilities.isHostnameReachable("www.apple.com")) {
//If host is not reachable, display a
UIAlertController informing the user
var alert = UIAlertController(title: "Alert", message: "You are
not conected to the Internet", preferredStyle: UIAlertControllerStyle.Alert)
//Add alert action
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
//Present alert
self.presentViewController(alert,
animated: true, completion: nil)
}
}
@IBAction func search(AnyObject) {
//Get text for search
var text: String = searchTextField.text;
//Convert spaces to "+"
let searchText = text.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch,
range: nil)
//Set the parameters for the
RSTransaction object
rsTransGet.parameters = ["term":searchText,"media":"music"]
//Send request
rsRequest.dictionaryFromRSTransaction(rsTransGet,
completionHandler: {(response : NSURLResponse!,
responseDictionary: NSDictionary!, error: NSError!) -> Void in
if !error? {
//Set the tableData NSArray to
the results that were returned from the iTunes search and reload the table
self.tableData = responseDictionary["results"] as NSArray
self.resultsTableView.reloadData()
} else {
//If there was an error, log it
println("Error : \(error)")
}
})
}
override func
didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can
be recreated.
}
//UITableView delegate methods are
below:
func tableView(tableView: UITableView!,
numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(tableView: UITableView!,
cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let kCellIdentifier: String = "MyCell"
//tablecell 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 the data from the NSArray
var rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary
//Set the text of the cell
cell!.textLabel.text = rowData["trackName"] as? String
//Set the detailText of the cell
cell!.detailTextLabel.text = rowData["trackCensoredName"] as NSString
//Use the setImageForURL method added
to the UIImageView by the
//RSNetworking catagory to load an
image from a URL.
//While the image loads we use a
placeholder image
var imageURL: NSString = rowData["artworkUrl60"] as NSString
var mCell = cell
mCell!.imageView.setImageForURL(imageURL,
placeHolder: UIImage(named: "loading"))
return cell
}
}