Thursday, January 13, 2011

Consume REST Web Service using iPhone

In this tutorial we will create an iPhone application which will consume REST Web Service.

The web service which will be consumed is developed in this post.

Our final application should looked like this.
This note is taken from a REST web service which taken its data from a database server.

First, create a new Window Based Application. Use "Notes" as the project name.

Next, create a new UIViewController subclass, check the UITableViewController subclass in the options panel but leave With XIB for user interface unchecked.
Use "NotesTableViewController" as the name.
Create several variables and a method in NotesTableViewController.h

#import <UIKit/UIKit.h>
@interface NotesTableViewController : UITableViewController {
NSMutableString *contentString;
NSMutableArray *notes;
NSMutableData *xmlData;
NSURLConnection *connectionInProgress;
}
- (void)loadNotes;
@end


Then, we will implement the method in NotesTableViewController.m

#import "NotesTableViewController.h"
@implementation NotesTableViewController
#pragma mark -
#pragma mark Initialization
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
notes = [[NSMutableArray alloc] init];
}
// Get the tab bar item
UITabBarItem *tbi = [self tabBarItem];
[tbi setTitle:@"Notes List"];
return self;
}
- (void)loadNotes {
[notes removeAllObjects];
[[self tableView] reloadData];
// Construct the web service URL
NSURL *url = [NSURL URLWithString:@"http://203.247.166.88:8000/NoteWS/notes"];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30];
// Clear out the existing connection if there is one
if (connectionInProgress) {
[connectionInProgress cancel];
[connectionInProgress release];
}
// Instantiate the object to hold all incoming data
[xmlData release];
xmlData = [[NSMutableData alloc] init];
// Create and initiate the connection - non blocking
connectionInProgress = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:YES];
}
// Kick off the loading whenever NotesTableViewController's table view appears
// on the screen by overriding viewWillAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadNotes];
}
// This method will be called several times as the data arrives
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[xmlData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Create the parser object with the data received from the web service
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
// Give it a delegate
[parser setDelegate:self];
// Tell it to start parsing - the document will be parsed and the delegate
// of NSXMLParser will get all of its delegate messages sent to it before
// this line of execution - it is blocking
[parser parse];
// The parser is done (it blocks until done), you can release it immediately
[parser release];
[[self tableView] reloadData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[connectionInProgress release];
connectionInProgress = nil;
[xmlData release];
xmlData = nil;
NSString *errorString = [NSString stringWithFormat:@"Fetch failed: %@", [error localizedDescription]];
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:errorString
delegate:nil
cancelButtonTitle:@"OK"
destructiveButtonTitle:nil
otherButtonTitles:nil];
[actionSheet showInView:[[self view] window]];
[actionSheet autorelease];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
if ([elementName isEqual:@"content"]) {
NSLog(@"found content!");
contentString = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[contentString appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqual:@"content"]) {
NSLog(@"ended content: %@", contentString);
[notes addObject:contentString];
// Release and nil contentString so that the next time characters
// are found and not within a content tag, they are ignored
[contentString release];
contentString = nil;
}
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [notes count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:@"UITableViewCell"] autorelease];
}
[[cell textLabel] setText:[notes objectAtIndex:[indexPath row]]];
return cell;
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc. that aren't in use.
}
- (void)dealloc {
[super dealloc];
}
@end


Create another UIViewController subclass for handling new note insertion. This time, uncheck the UITableViewController subclass but check the With XIB for user interface. Use "NotesInsertViewController" as the name

Next, create some outlet and a method in our controller. Modify "NotesInsertViewController.h"

#import <UIKit/UIKit.h>
@interface NotesInsertViewController : UIViewController {
IBOutlet UITextField *idField;
IBOutlet UITextField *contentField;
}
-(IBAction)insertNote:(id)sender;
@end


Then, double click NotesInsertViewController.xib to open Interface Builder.

Create two UITextField and one Rounded Button.
Connect both UITextField with the appropriate field: idField and contentField
Connect Rounded Button with the insertNote action

Next, implement the method in NotesInsertViewController.m

#import "NotesInsertViewController.h"
@implementation NotesInsertViewController
- (id)init {
[super initWithNibName:@"NotesInsertViewController"
bundle:nil];
UITabBarItem *tbi = [self tabBarItem];
[tbi setTitle:@"Insert Note"];
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
return [self init];
}
- (IBAction)insertNote:(id)sender {
NSString *post = [NSString stringWithFormat:@"noteId=%@&content=%@&createddate=8", [idField text], [contentField text]];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:@"http://203.247.166.88:8000/NoteWS/notes"]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];
NSURLConnection *conn=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (conn)
{
}
else
{
}
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
@end


After this we can create the tab controller to navigate between the view table and insert view
Open and edit NotesAppDelegate.h

#import <UIKit/UIKit.h>
@interface NotesAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@end


Create a UITabBarController and assign each tab item with the appropriate controller. Open and edit NotesAppDelegate.m

#import "NotesAppDelegate.h"
#import "NotesTableViewController.h"
#import "NotesInsertViewController.h"
@implementation NotesAppDelegate
@synthesize window;
#pragma mark -
#pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create the tabBarController
tabBarController = [[UITabBarController alloc] init];
// Create two view controllers
UIViewController *notesTable = [[NotesTableViewController alloc] initWithStyle:UITableViewStylePlain];
UIViewController *notesInsert = [[NotesInsertViewController alloc] init];
// Make an array containing the two view controllers
NSArray *viewControllers = [NSArray arrayWithObjects:notesInsert, notesTable, nil];
[notesTable release];
[notesInsert release];
// Attach them to the tab bar controller
[tabBarController setViewControllers:viewControllers];
// Put the tabBarController's view on the window
[window addSubview:[tabBarController view]];
[self.window makeKeyAndVisible];
return YES;
}
- (void)dealloc {
[window release];
[super dealloc];
}
@end


Build & run our application.
Notice that in inserting a new note we hardcode the date as a string and put 8 which will be interpreted by the web service as "8 seconds after 1 January 1970"

It is a little buggy and the view might not as good. But the point of the tutorial is on consuming REST Web Service, so i decide to make a minimum design and focus on the methods for handling Web Service.

Anyways, enjoy playing with REST Web Service!

3 comments: