Building an iPhone chat app from the ground up

Make a cool iPhone app and its corresponding server piece

In this article, work through the entire process of building an iPhone chat application from the server all the way to the user interface on the front end.

Jack D. Herrington, Senior Software Engineer, Fortify Software, Inc.

Photo of Jack HerringtonJack Herrington is an engineer, author, and presenter who lives and works in the Bay Area. You can keep up with his work and his writing at http://jackherrington.com.



05 January 2011

Also available in Chinese Russian Japanese Portuguese

Architecting an iPhone chat app

Frequently used acronyms

  • DOM: Document Object Model
  • IDE: Integrated development environment
  • SAX: Simple API for XML
  • SQL: Structured Query Language
  • UI: User interface
  • W3C: World Wide Web Consortium
  • XIB: Xml Interface Builder
  • XML: Extensible Markup Language

With an installed base of 40 million iPhones, you have to be crazy if you aren't interested in writing an iOS application. But where do you start? Most applications are going to be network connected. So what about a project that spans across both, such as a chat application. In this article I show you how to build a chat application with both server and client components. You can learn much from it about creating iOS applications that I guarantee you will want to write one by the end of this article.

Building the application starts with architecting the solution. Figure 1 shows the architecture of how the iOS device (the iPhone in this case) connects to the server through two PHP pages.

Figure 1. The Chat App client/server architecture
Diagram of iPhone client with add.php and messages.php pages connected to a database


These two PHP pages, add.php and messages.php, both connect to the database to both post and retrieve the messages, respectively. In the code that I provide, the database is MySQL, but you can use DB2 or any other database that you like.

The protocol I use is XML. The add.php page returns an XML message that says whether the message post has been successful. And the messages.php page returns the latest messages posted to the server.

Before you start, I want to cover what you will learn here.

  • Database access. I show you how to use PHP to add rows to the database and retrieve them.
  • XML encoding. The server code demonstrates how to package up the messages into XML.
  • Building an iOS interface. I go through building the user interface for the application.
  • Querying the server. The Objective-C code makes GET requests to the messages.php page to get the latest chat messages.
  • Parsing the XML. Using the XML parser available to iOS developers you can parse the XML returned from messages.php.
  • Displaying the messages. The application uses a custom list item to display the chat messages; this approach can give you some insight into how to customize the look and feel of your iOS application.
  • Posting a message. The application POSTs data to the server through add.php, which guides you through that process.
  • Timers. A timer task is used to periodically poll messages.php to see when new chat items arrive.

That's a lot for one example and it should provide a decent set of tools for you to develop any type of client/server iOS application you want to build.


Building the server

You start by creating the database. I called mine "chat," but you can call yours whatever you like. You just need to make sure that you change the connection strings in the PHP to match the name of the database. The SQL script used to build the single table for the application is in Listing 1.

Listing 1. chat.sql
DROP TABLE IF EXISTS chatitems;
CREATE TABLE chatitems (
    id BIGINT NOT NULL PRIMARY KEY auto_increment,
    added TIMESTAMP NOT NULL,
    user VARCHAR(64) NOT NULL,
    message VARCHAR(255) NOT NULL
);

This simple single-table database has just four fields:

  • The id of the row, which is just an auto-incrementing integer
  • The date the message was added
  • The user that added the message
  • The text of the message itself

You can change the sizes of these fields to accommodate your content.

In a production system, you likely want to also have a user table with names and passwords and have a login user interface, and so on. For this example, I wanted to keep the database simple, so there is only a single table.

The first thing you want to build is the add.php script in Listing 2.

Listing 2. add.php
<?php
header( 'Content-type: text/xml' );
mysql_connect( 'localhost:/tmp/mysql.sock', 'root', '' );
mysql_select_db( 'chat' );
mysql_query( "INSERT INTO chatitems VALUES ( null, null, '".
    mysql_real_escape_string( $_REQUEST['user'] ).
    "', '".
    mysql_real_escape_string( $_REQUEST['message'] ).
    "')" );
?>
<success />

This script connects to the database and stores the message using the posted user and message fields. It's a simple INSERT statement where the two values are escaped to account for any errant characters such as single quotes that might disturb the SQL syntax.

To test the add script, you create a test.html page, shown in Listing 3, that simply posts fields to the add.php script.

Listing 3. test.html
<html>
<head>
    <title>Chat Message Test Form</title>
</head>
<body>
    <form action="add.php" method="POST">
        User: <input name="user" /><br />
        Message: <input name="message" /><br />
        <input type="submit" />
    </form>
</body>
</html>

This simple page has only one form that points to add.php, with the two text fields for the user and the message. It then has the Submit button that executes the post.

With the test.html page installed, you can test the add.php script. Bringing up the test page in the browser looks something like Figure 2, with the value of '"jack" in the User field displays, the value of "This is a test" in the Message field, and the Submit Query button.

Figure 2. The message posting test page
Screen capture of page with User and Message fields plus Submit Query button

From here, you add in some values and click the Submit Query button. If all works well you see something like Figure 3.

Figure 3. A successful message post
Screen capture of XML file viewed in browser with a <success/> element

If not, you likely get a PHP stack trace, informing you that the database connection failed or the INSERT statement did not work.

With the message add script working, it's time to build the messages.php script, which returns the list of messages. This script is shown in Listing 4.

Listing 4. messages.php
<?php
header( 'Content-type: text/xml' );
mysql_connect( 'localhost:/tmp/mysql.sock', 'root', '' );
mysql_select_db( 'chat' );
if ( $_REQUEST['past'] ) {
    $result = mysql_query('SELECT * FROM chatitems WHERE id > '.
        mysql_real_escape_string( $_REQUEST['past'] ).
        ' ORDER BY added LIMIT 50');
} else {
    $result = mysql_query('SELECT * FROM chatitems ORDER BY added LIMIT 50' );    
}
?>
<chat>
<?php
while ($row = mysql_fetch_assoc($result)) {
?>
<message added="<?php echo( $row['added'] ) ?>" id="<?php echo( $row['id'] ) ?>">
    <user><?php echo( htmlentities( $row['user'] ) ) ?></user>
    <text><?php echo( htmlentities( $row['message'] ) ) ?></text>
</message>
<?php
}
mysql_free_result($result);
?>
</chat>

This script is a little more complicated. The first thing it does is put together the query. There are two possibilities here:

  • If a past parameter was provided, then the script returns only messages past the specified ID.
  • If no past parameter is specified, then all of the messages are returned.

The reason for the past parameter is that you want clients to be smart. You want the client to remember what messages it's already seen and ask for only those messages past the ones it already has. The client logic is easy enough, it just keeps the highest value ID that it finds and sends that as the past parameter. In the beginning it can send 0 as the value, which is the same as specifying nothing at all.

The second half of the script does the retrieval of the records from the query result set and encodes them into XML. If this part of the script works, then you see something similar to Figure 4 when you go to the page in your browser of choice.

Figure 4. The chat message list
Screen capture of short chat message coded in XML, includes timestamp, ID, user, and message text

That's everything you need for the server. Of course, you can add whatever logic you want, extra channels, user validation and login, and so on. For the purposes of this experimental chat app, this script works nicely. Now you can build the iOS application that will use this server.


Building the client

The iOS IDE is called XCode. If you don't have it, you need to download it from the Apple Developer Site (see Resources). The most recent production version is XCode 3, and that's what I use for the screen captures here. There is a newer version called XCode 4, which integrates the User Interface editor into the IDE, but that version is still in preview mode at this point.

With XCode installed, it's time to build the application using the New Project wizard as in Figure 5.

Figure 5. Building a view-based iPhone app
Screen capture of selected template options: iPhone OS > Application and Product > iPhone > View-based Application

The easiest type of application to start with is a view-based application. This kind of app allows you to place controls wherever you choose and leaves most of the UI design to you. After you select the controls, select either iPhone or iPad. This choice is concerned with the device you want to simulate on. You can write the code so that it works on iPhone or iPad, or any other i-device Apple comes up with next.

After you click Choose, you are asked to name the application. I called mine "iOSChatClient," but you can name it whatever you like. After you give it a name, the XCode IDE builds the core application files. From here, compile it once and start it up just to make sure that everything looks right.

Creating the user interface

After the application is created, you can develop the interface. The place to start is with the view controller XIB file, which is located in the Resources folder. By double-clicking on that folder, you bring up the Interface Builder which is the UI toolkit.

Figure 6. The interface layout
Screen capture of UI with a message text box, Send button, and list of chat items

Figure 6 shows how I laid out the three controls. At the top is a text box for entering the message you want to send. To the right of that text box is the Send button. And below that is a UITableView object that shows all the chat items.

I could go into detail about how to set all this up in Interface Builder, but I recommend that you download the project code and play with it yourself. Feel free to use the project as a template for your own app.

Creating the view controller

That's really it for the user interface. The next task is to go back to the XCode IDE and add some member variables, properties, and methods to the view controller class definition as in Listing 5.

Listing 5. iOSChatClientViewController.h
#import <UIKit/UIKit.h>

@interface iOSChatClientViewController : UIViewController 
                             <UITableViewDataSource,UITableViewDelegate>    {
    IBOutlet UITextField *messageText;
    IBOutlet UIButton *sendButton;
    IBOutlet UITableView *messageList;

    NSMutableData *receivedData;
    NSMutableArray *messages;
    int lastId;

    NSTimer *timer;

    NSXMLParser *chatParser;
    NSString *msgAdded;
    NSMutableString *msgUser;
    NSMutableString *msgText;
    int msgId;
    Boolean inText;
    Boolean inUser;
}

@property (nonatomic,retain) UITextField *messageText;
@property (nonatomic,retain) UIButton *sendButton;
@property (nonatomic,retain) UITableView *messageList;

- (IBAction)sendClicked:(id)sender;

@end

From the top, I added UITableViewDataSource and UITableViewDelegate to the class definition. This code is used to drive the message display. There are methods in the class that get called back to feed both data and layout information to the table view.

The instance variables are broken into five groups. At the top are the object references to the various UI elements, the text field for the message to send, the send button, and the message list.

Below that is the buffer to hold the returned XML, the list of messages, and the last ID seen. That lastID starts out at zero but gets set to the maximum ID value that you see for any messages. It's then sent back to the server as the value of the past parameter.

The timer is what fires every couple of seconds to look for new messages from the server. And the last section includes all the member variables needed to parse the XML. There are a lot of them because the XML parser is a callback-based parser, which means that it holds a lot of state in the class.

Below the member variables are the properties and the click handler. These are used by the Interface Builder to hook up the interface elements to this controller class. In fact, with all this in the view controller, it would be a good time to go back to the Interface Builder and use the connector controls to connect the message text, send button, and message list to their corresponding properties, and to connect the Touch Inside event to the sendClicked method.

Building the view controller code

With the view controller header out of the way, you're ready to dig into the meat of the project and to implement the view controller. I break this up across several listings even though it's just in one file so that I can explain each section a little more easily.

The first section, Listing 6, covers the application startup and the initialization of the view controller.

Listing 6. iOSChatClientViewController.m – Starting up
#import "iOSChatClientViewController.h"

@implementation iOSChatClientViewController

@synthesize messageText, sendButton, messageList;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
        lastId = 0;
        chatParser = NULL;
    }
    return self;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:
                                       (UIInterfaceOrientation)interfaceOrientation {
    return YES;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
}

- (void)dealloc {
    [super dealloc];
}

This is pretty standard iOS code. There are some callbacks for variable system events such as memory warnings and the de-allocation. In a production app, you want to handle all of these items gracefully, but for the purposes of this example application I didn't want to overcomplicate matters.

The first real task comes in making the GET request to the messages.php script. Listing 7 shows the code for this.

Listing 7. iOSChatClientViewController.m – Getting the messages
- (void)getNewMessages {
    NSString *url = [NSString stringWithFormat:
        @"http://localhost/chat/messages.php?past=%ld&t=%ld",
        lastId, time(0) ];

    NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
    [request setURL:[NSURL URLWithString:url]];
    [request setHTTPMethod:@"GET"];

    NSURLConnection *conn=[[NSURLConnection alloc] initWithRequest:request delegate:self];
    if (conn)
    {
        receivedData = [[NSMutableData data] retain];
    }
    else
    {
    }
}

- (void)connection:(NSURLConnection *)connection
  didReceiveResponse:(NSURLResponse *)response
{  
    [receivedData setLength:0];
}  

- (void)connection:(NSURLConnection *)connection 
  didReceiveData:(NSData *)data  
{  
    [receivedData appendData:data];
}  

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{  
    if (chatParser)
        [chatParser release];

    if ( messages == nil )
        messages = [[NSMutableArray alloc] init];

    chatParser = [[NSXMLParser alloc] initWithData:receivedData];
    [chatParser setDelegate:self];
    [chatParser parse];

    [receivedData release];

    [messageList reloadData];

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
    [self methodSignatureForSelector: @selector(timerCallback)]];
    [invocation setTarget:self];
    [invocation setSelector:@selector(timerCallback)];
    timer = [NSTimer scheduledTimerWithTimeInterval:5.0 
        invocation:invocation repeats:NO];
}

- (void)timerCallback {
    [timer release];
    [self getNewMessages];
}

The code starts with the getNewMessages method. This method creates the request and starts it by building an NSURLConnection. It also creates the data buffer that holds the response data. The three event handlers, didReceieveResponse, didReceiveData, and connectionDidFinishLoading, all handle the various phases of loading the data.

The connectionDidFinishLoading method is the most important because it starts the XML parser that reads through the data and picks out the messages.

The final method here, timerCallback, is used by the timer to start the request of the new message. When the timer goes off, the getNewMessages method is called, which starts the process again, culminating with creating a new timer that when it times out starts the message retrieval process again, and so on.

The next section, Listing 8, deals with the parsing of the XML.

Listing 8. iOSChatClientViewController.m – Parsing the messages
- (void)parser:(NSXMLParser *)parser 
didStartElement:(NSString *)elementName 
namespaceURI:(NSString *)namespaceURI 
qualifiedName:(NSString *)qName 
attributes:(NSDictionary *)attributeDict {
    if ( [elementName isEqualToString:@"message"] ) {
        msgAdded = [[attributeDict objectForKey:@"added"] retain];
        msgId = [[attributeDict objectForKey:@"id"] intValue];
        msgUser = [[NSMutableString alloc] init];
        msgText = [[NSMutableString alloc] init];
        inUser = NO;
        inText = NO;
    }
    if ( [elementName isEqualToString:@"user"] ) {
        inUser = YES;
    }
    if ( [elementName isEqualToString:@"text"] ) {
        inText = YES;
    }
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if ( inUser ) {
        [msgUser appendString:string];
    }
    if ( inText ) {
        [msgText appendString:string];
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
   namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ( [elementName isEqualToString:@"message"] ) {
        [messages addObject:[NSDictionary dictionaryWithObjectsAndKeys:msgAdded,
            @"added",msgUser,@"user",msgText,@"text",nil]];

        lastId = msgId;

        [msgAdded release];
        [msgUser release];
        [msgText release];
    }
    if ( [elementName isEqualToString:@"user"] ) {
        inUser = NO;
    }
    if ( [elementName isEqualToString:@"text"] ) {
        inText = NO;
    }
}

This XML parser should be familiar to anyone who knows SAX parsing. You give it some XML, and it calls you back when tags are opened, or closed, when text is found, and so on. It's an event-based parser rather than a DOM-based parser. Event parsers have the advantage that they have a light memory footprint. But the disadvantage is that they are a bit harder to use because all of the state needs to be stored in the host object during parsing.

The process starts with all of the member variables such as msgAdded, msgUser, inUser, and inText initialized to an empty string or false. Then as each of the tags is started in the didStartElement method, the code looks at the tag name and sets the appropriate inUser or inText Boolean. From there, the foundCharacters method handles adding the text data to the appropriate string. The didEndElement method then handles the close of the tag by adding the parsed message to the message list when the end of the <message> tag is found.

Now you need some code to display the messages. This is shown in Listing 9.

Listing 9. iOSChatClientViewController.m – Displaying the messages
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)myTableView numberOfRowsInSection:
                                       (NSInteger)section {
    return ( messages == nil ) ? 0 : [messages count];
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:
                                       (NSIndexPath *)indexPath {
    return 75;
}

- (UITableViewCell *)tableView:(UITableView *)myTableView 
   cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = (UITableViewCell *)[self.messageList 
        dequeueReusableCellWithIdentifier:@"ChatListItem"];
    if (cell == nil) {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ChatListItem" 
            owner:self options:nil];
        cell = (UITableViewCell *)[nib objectAtIndex:0];
    }

    NSDictionary *itemAtIndex = (NSDictionary *)[messages objectAtIndex:indexPath.row];
    UILabel *textLabel = (UILabel *)[cell viewWithTag:1];
    textLabel.text = [itemAtIndex objectForKey:@"text"];
    UILabel *userLabel = (UILabel *)[cell viewWithTag:2];
    userLabel.text = [itemAtIndex objectForKey:@"user"];

    return cell;
}

These are all of the methods defined by the UITableViewDataSource and UITableViewDelegate interfaces. The most important one is the cellForRowAtIndexPath method, which creates a custom UI for the list item and sets its text fields to the appropriate text for that message.

This custom list item is defined in a new ChatListItem.xib file that you need to create in the Resources folder. In that file, you create a new UITableViewCell item that has two labels in it, tagged 1 and 2. This file, along with all the other code, is available in the downloadable project (see Download).

The code in the cellForRowAtIndexPath method allocates one of these ChatListItem cells, then sets the text field for the labels to the text and user values that we found for that message.

I know it's a lot to take in, but you are almost at the end. You already have the code to start up the view, get the message XML, parse it, and display the messages. The only thing that remains is to write the code that sends the message.

The first part of building that code is to create a setting for the user name. iOS applications can define custom settings that go in the Settings control panel. To create a setting you need to use the New File wizard to create a settings bundle in the Resources folder. Then you delete it down to just a single setting using the settings editor in Figure 7.

Figure 7. Setting up the settings
Screen capture of the settings editor

Then you define that you want the setting to be titled User and have the key user_preference. From there, you can use this preference to get the user name for the message sending code in Listing 10.

Listing 10. iOSChatClientViewController.m – Sending the message
- (IBAction)sendClicked:(id)sender {
    [messageText resignFirstResponder];
    if ( [messageText.text length] > 0 ) {
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

        NSString *url = [NSString stringWithFormat:
            @"http://localhost/chat/add.php"];

        NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] 
            init] autorelease];
        [request setURL:[NSURL URLWithString:url]];
        [request setHTTPMethod:@"POST"];

        NSMutableData *body = [NSMutableData data];
        [body appendData:[[NSString stringWithFormat:@"user=%@&message=%@", 
             [defaults stringForKey:@"user_preference"], 
             messageText.text] dataUsingEncoding:NSUTF8StringEncoding]];
        [request setHTTPBody:body];

        NSHTTPURLResponse *response = nil;
        NSError *error = [[[NSError alloc] init] autorelease];
        [NSURLConnection sendSynchronousRequest:request 
            returningResponse:&response error:&error];

        [self getNewMessages];
    }

    messageText.text = @"";
}

- (void)viewDidLoad {
        [super viewDidLoad];

    messageList.dataSource = self;
    messageList.delegate = self;

    [self getNewMessages];
}

@end

This is the click handler code for the Send Message button. It creates a NSMutableURLRequest that has the URL for the add.php script. It then sets the body of the message to a string that has the user and message data encoded in POST format. It then uses an NSURLConnection to synchronously send the message data to the server and starts a message retrieval using getNewMessages.

The viewDidLoad method at the bottom of this file is what is called when the view loads. It starts the message retrieval process and connects the message list with this object so that the message list knows where to get its data.

With all of this coded, it's time to test the application. That starts with setting the user name in the Settings page in Figure 8.

Figure 8. The Settings page
Screen capture of Setting page with General, Safari, Photos, and iOSChatClient options

From here, you click the iOSChatClient application, and the settings page in Figure 9 displays.

Figure 9. Setting the user name
Screen capture of iOSChatClient settings page with value of 'Megan' in the User field

Then you return to the application just as you would on the phone and type a message using the keyboard as in Figure 10.

Figure 10. Typing a new message
Screen capture of QWERTY keyboard, message text box, Send button, and a test message from user jack

Then pressing the send button we see that the message went to the server, was posted, and then returned from messages.php as you can see in Figure 11.

Figure 11. The completed chat application
Screen capture of message text box, Send button, and two test messages from users jack and megan

You'll note from the code that there is no direct connection between the send button and the message list. So the only way for the message to make it into the list is through the server successfully inserting the data into the database. Then the message.php code successfully returns the message for display in the message list.


Conclusions

It's certainly a lot to take in, but you learned a lot from this article. You did some database work with XML on the backend. You built an iOS application with a custom user interface that sends and retrieves data from the server. You used the XML parser to chew up the response XML from the server. And you built a custom list UI to make the messages look pretty.

Where you go next with this is up to you. Apple has given you the tools to implement whatever vision you want on the iPhone or the iPad. And this article has given you a roadmap to building your own network enabled app. I encourage you to jump in and give it a try. If you do build something cool please let me know and I'll be sure to check it out on the App Store.


Download

DescriptionNameSize
Source code for articleChatProject.zip29KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source, Industries
ArticleID=604467
ArticleTitle=Building an iPhone chat app from the ground up
publish-date=01052011