After a conversation I had on stack overflow I realised that, while my previous tutorial on storyboards has been very useful to many in the community, a real world example was what was really required to help people build a deeper understanding of the concept.

This tutorial is a simple one focused around a task list, with a simple LAMP backend. For this tutorial I will assume that you have Php, MySql and whichever server technology you wish to use – I use Apache. You can download the source for MyTasks here and the database sql schema from here

Step 1: My Tasks iOS 5 client using Storyboards
Create a new Single View Applicaition project in Xcode. Name it MyTasks and make sure you have Storyboard and ARC checked. The default Scene created when you create the project extends UIViewController and we want a UITableViewController for this application. The quickest way to do this is just to delete the default UIViewController class and scene, created a new UITableViewController class called SSTableViewController and pull on a new UITableView onto your Storyboard, creating a new scene, and set its class to SSTableViewController in the Identity Inspector.

To communicate the with server we use HTTP Post. For speed you should download Mr Gando’s SimplePost and drag this into you project. SimplePost creates multi-part NSMutableRequests that you can use to post dictionaries of data to your web service – importantly it works with ARC. To complete the scaffolding for this Xcode project create a Task class for representing your tasks.

.h
 
#import <Foundation/Foundation.h>
 
@interface Task : NSObject
{
    NSInteger taskID;
    NSString *name;
}
@property(assign,nonatomic)NSInteger taskID;
@property(strong,nonatomic)NSString *name;
 
-(id)initWithDictionary:(NSDictionary *)dict;
-(id)initWithName:(NSString *)aName;
-(NSDictionary *)toDictionary;
@end

Create a constructor that can build an instance of the class from the dictionary returned from the web service. Also create a toDictionary method to serialise the objected so that it can be posted to the web service.

.m
#import "Task.h"
 
@implementation Task
 
@synthesize name,taskID;
 
-(id)init{
    return [self initWithName:nil ];
}
 
-(id)initWithDictionary:(NSDictionary *)dict{
    if(self!=nil){
        self.taskID = [[dict objectForKey:@"taskID"] integerValue];
        self.name = [dict objectForKey:@"name"];
    }
    return self;
}
 
-(id)initWithName:(NSString *)aName{
    if(self!=nil){
        NSDate *date = [NSDate date];       
        self.taskID = [date hash];
        self.name = aName;
    }
    return self;
}
 
-(NSDictionary *)toDictionary{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:self.name forKey:@"name"];
    [dict setObject:[[NSNumber numberWithInteger:self.taskID] stringValue] forKey:@"taskID"];
    return dict;
}
@end

Now lets look at the key to this tutorial, the Storyboard. From our previous setup we have a UITableViewController for displaying a list of tasks so an important feature will be for people to be able to add a task. So we will add a plus button to the initial scene in our storyboard so that we can support users in creating new tasks. To add this button we are going to embed the SSTableViewController in a UINavigationController by selecting the UITableViewController and going to the menu and selecting Editor->Embed In-> Navigation Controller. Then drag on a UIBarButtonItem on to the top right hand side of the navigation bar, in the attribute inspector change the identifier of the UIBarButtonItem to Add.

You will notice that by embedding your UITableViewController in a UINavigationController a new scene is added to the storyboard and a relationship between the two is created. The first scene is the navigation controller and the link is the relationship between that and the table view. Now lets create a new task scene. Start by dragging a new UIViewController onto your Storyboard. We want to have a cancel button incase a user changes their mind and does not want to add a new task and a done button to confirm they have finished creating the new task. So, once again, we embed this UIViewController in a UINavigationController and drag on two UIBarButtonItems, cancel and done onto it.

Now we want to display the new task UIViewController when the Add button is pressed so we right click on the UIBarButtonItem and drag across to the UINavigationController of our new UIViewController and choose modal from the resulting popup. You have now successfully created a Segue. If you run the application and tap the Add button the new task scene will be presented modally. Finally add a label and a textfield to the new task view so that users can add a name for the new task.

We now need to create a class for this New Task UIViewController (NewTaskViewController). This must implement the UITextFieldDelegate to enable us to handle any edits and contain an outlet for the text field for capturing the text being created for the task name. We are going to use the delegation pattern to allow another controller to decide how to handle a New Task canceled event and New Task created event so we must add a protocol and a delegate that implements this protocol.

.h
#import <UIKit/UIKit.h>
#import "Task.h"
 
@protocol NewTaskDelegate;
 
@interface NewTaskViewController : UIViewController <UITextFieldDelegate>
{
    id<NewTaskDelegate>delegate;
    UITextField *taskNameField;
}
 
@property(strong,nonatomic)id<NewTaskDelegate>delegate;
@property(strong,nonatomic)IBOutlet UITextField *taskNameField;
 
-(IBAction)cancel:(id)sender;
-(IBAction)done:(id)sender;
 
@end
 
@protocol NewTaskDelegate <NSObject>
 
-(void)newTaskViewController:(NewTaskViewController *)ntvc didCreateNewTask:(Task *)task;
-(void)cancelNewTaskViewController:(NewTaskViewController *)ntvc;
 
@end
.m
#import "NewTaskViewController.h"
 
@implementation NewTaskViewController
 
@synthesize delegate,taskNameField;
 
- (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.
}
 
#pragma mark - Actions
-(IBAction)cancel:(id)sender{
    [self.delegate cancelNewTaskViewController:self];
}
 
-(IBAction)done:(id)sender{
    if([self.taskNameField.text length]<=0)
    {
        NSLog(@"You have not entered a name for this task %@",self.taskNameField.text);
        return;
    }
    Task *newTask = [[Task alloc] initWithName:taskNameField.text];  
    [self.delegate newTaskViewController:self didCreateNewTask:newTask];
}
 
#pragma mark - TextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    return YES;
}
 
#pragma mark - View lifecycle
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
 
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
 
- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}
 
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}
 
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}
 
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}
 
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
}
 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
	return YES;
}
@end

Your main UITableViewController (SSTableViewController) must implement the NewTaskDelegate protocol so that it can receive the notifications of new tasks created or canceled. Once we have done so we must then set it as the delegate to the NewTaskViewController, this is done in the prepareforSegue method. In this method you must first identify the Segue that has been activated by looking at its identifier. To do this you must set its identifier by selecting the Segue in your storyboard and in the attribute inspector change its name to NewTask.

Now implement the prepareForSegue method in the SSTableViewController using the identifier to ensure you are setting things up on the correct Segue. For the NewTask Segue we know that the UIViewController being presented is a UINavigationController so we have to get that using the segue’s destinationViewController method and then pull the topViewController from that to get the NewTaskViewController where we can then set ourself as its delegate, like so.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"NewTask"]){
        UINavigationController *nv = (UINavigationController *)[segue destinationViewController];
        NewTaskViewController *ntvc = (NewTaskViewController *)nv.topViewController;
        ntvc.delegate = self;
    }
}

The final piece of the jigsaw is to finish the implementation of the SSTableViewController to retrieve the tasks pending and updating of completed tasks.

.h
 
@interface SSViewController : UITableViewController <NewTaskDelegate>
{
    NSArray *pendingTasks;
}
 
@property(strong,nonatomic) NSArray *pendingTasks;
-(void)addTask:(Task *)task;
-(void)refreshPendingTasks;
-(void)setTaskCompleted:(Task *)task;
@synthesize pendingTasks;
 
static NSString *kPendingTasks = @"http://localhost/~scott/my_tasks/pending_tasks.php";
static NSString *kSetTaskCompleted = @"http://localhost/~scott/my_tasks/set_task_completed.php";
static NSString *kAddTask = @"http://localhost/~scott/my_tasks/add_task.php";
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}
 
#pragma mark - NewTaskDelegate
 
-(void)newTaskViewController:(NewTaskViewController *)ntvc didCreateNewTask:(Task *)task{
    [self.navigationController dismissModalViewControllerAnimated:YES];
    [self addTask:task];
}
 
-(void)cancelNewTaskViewController:(NewTaskViewController *)ntvc{
    [self.navigationController dismissModalViewControllerAnimated:YES];
}
 
#pragma mark - NSURLConnectionDelegate
 
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
        NSArray *array = (NSArray *)[NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:0 errorDescription:nil];
        NSMutableArray *ma = [NSMutableArray array];
        for(int i=0;i<[array count];i++){
            NSDictionary *d = (NSDictionary *)[array objectAtIndex:i];
            Task *task = [[Task alloc] initWithDictionary:d];
            [ma addObject:task];
        }
        self.pendingTasks = ma;
        [self.tableView reloadData];    
}
 
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"did fail");
}
 
#pragma mark -Server Actions
 
-(void)addTask:(Task *)task{
    NSMutableURLRequest *request = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kAddTask] andDataDictionary:[task toDictionary]];
    [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
 
-(void)setTaskCompleted:(Task *)task{
    NSMutableURLRequest *request = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kSetTaskCompleted] andDataDictionary:[task toDictionary]];
    [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

Step 2: Communications
This section relies on a modified version of jsjohnst’s plist parser. The modified plist parser is available in the project bundle.

Three process are required from the server,

  • Add a Task (add_task.php)
  • Retreive Pending Tasks (pending_tasks.php)
  • Mark task as Complete (set_task_completed.php)

To add a task we must pass the task from the iOS client to the server. In this example the client itself ensures that each new task has a unique identifier by using the date and time that it is created, however for a more robust solution you may want to have this done on the server to ensure that every task is unique, particular if you have multiple users.

After receiving the taskID and name we call the addTask method defined in our functions file and request the pending tasks and pass them back to the client. This will result in the recently added task being added to the list and send back with all the pending tasks. Again this could be refined and the new task might be created and stored on the client once a confirmation has been received that the server has been successfully updated rather than downloading the entire list again.

Add a Task

<?php
 
require_once('PlistParser.php');
require_once('functions.php');
 
$taskID = $_POST['taskID'];
$name = $_POST['name'];
 
addTask($taskID,$name);
$pending_tasks = getPendingTasks();
 
$pp = new PlistParser();
$plist = $pp->convertIntoPlist($pending_tasks,true);
echo $plist;

Before moving on it is important to note the use of the PlistParser. This takes an array of tasks (in this case the tasks are represented as dictionaries returned by the getPendingTasks) and constructs a plist which is extremely simple to parse on the iOS client as we will see a little later. The pending_tasks.php and set_task_completed.php are very similar in structure but they call on different methods in the functions.php file. Lets take a little look at the functions file and the addTask, getPendingTasks and taskCompleted methods.

function addTask($taskID,$name){
     $link = mysql_connect('<server>', '<your username>', '<your password>');
    if (!$link) {
        die('Could not connect: ' . mysql_error());
    }
    mysql_select_db("my_tasks");
    $query = "INSERT INTO tasks (taskID, name, complete) VALUES ('$taskID', '$name', 'false')";    
    mysql_query($query);
    mysql_close($link);
}

This method connects to the database using mysql_connect, you must change the connection credentials to suit your setup. If the connection fails the method dies presenting an error otherwise it connects to the selected database. If you have named your database as specified previously this will be my_tasks. Finally this method takes the parameters passed and inserts them into the database closing the connection. The getPendingTasks method and the taskCompleted methods are very similar in structure the main difference is the database query.

All Pending Tasks

function getPendingTasks(){
    $pending_tasks = array();
 
    $link = mysql_connect('<server>', '<username>', '<password>');
    if (!$link) {
        die('Could not connect: ' . mysql_error());
    }
    mysql_select_db("my_tasks");
    $query = "SELECT taskID, name, complete FROM tasks WHERE complete=false";
    $result = mysql_query($query);
 
    while ($row = mysql_fetch_assoc($result)) {
        $task = array();
        $task["taskID"] = $row['taskID']; 
        $task["name"] = $row['name'];
        array_push($pending_tasks,$task);
    }
    mysql_close($link);
    return $pending_tasks;
}

Task Completed
Finally to mark a task as complete the method is as follows

function taskCompleted($taskID){
     $link = mysql_connect('<server>', '<username>', '<password>');
    if (!$link) {
        die('Could not connect: ' . mysql_error());
    }
    mysql_select_db("my_tasks");
    $query = "UPDATE tasks SET complete=true WHERE taskID='".$taskID."'";    
    mysql_query($query);
    mysql_close($link);
}

Step 3: Creating a MySQL Database
There are various ways to create a MySQL database, in this tutorial we will be using a database management tool called Seguel Pro (I highly recommend it, it is a really good tool). Should you want to load the database in your own tool you can get the sql schema from here, or you can quickly construct it yourself.

To construct the simple task database we have to create a table with three columns to store the task identifier, the task and its current status (completed or pending). This is done by following these steps,

  1. Create a new database, (Database->Add Database…),and enter my_tasks as the database name
  2. Add a new table to the database called tasks
  3. Add three columns, taskID (int), name (varchar 255), complete (tinyint)

This is an extremely simple database intended to be used to illustrate how to retrieve/update values via the iOS client. The next stage is to build the Php scripts that will enable the iOS client to interact with the database.

This completes the tutorial all of the source files can be downloaded from here allowing you to set up your won simple task server and using the iOS client to interact with this simple backend, I hope this helps.