Channels ▼


Making Two-Way Tables in iOS

In a previous DDJ article, Displaying Tabular Data on iPhones, I discussed how to use the UITableView class in Apple's iOS SDK to generate and present tables. Such tables can effectively display a lot of information on small screens, particularly the iPod Touch and iPhone. For some apps, simply presenting the data — such as stock market quotes, weather reports, price information, or an itinerary — is sufficient. However, for many business apps this is not enough: The table must implement a two-way flow of information. For example, a part-ordering app not only has to display what is in inventory, but also allow a user to enter a request that orders a part (or parts). This article helps close the communications loop by showing you how to enter and edit text in the table.

Although iOS has several Cocoa Touch classes that specialize in displaying text, the class of choice for our purpose is UITextField. This class is designed to capture and edit small amounts of text that the app then processes, usually immediately. UITextField does this by presenting a text string within an editable on-screen area or field. Visually, the field is perceived as editable because it is surrounded by a border — unlike text that belongs to a static label. When a user taps on the field, the UITextField first presents a mechanism (typically a virtual keyboard) that lets them modify the text. When the user completes editing the text, she taps the Return key. The keyboard disappears, and the UITextField instance then sends action messages to a delegate object for a response. In our case, when the keyboard is dismissed, the UITableView's delegate method writes the modified text back into the data model. The data model is typically a custom class that is responsible for storing and updating the information that the table displays. From there, to continue with the parts ordering example, the revised data might be sent over the air to a parts database server, where the appropriate scripts are launched and complete the transaction. Let's take a closer look at UITextField to see how we'll put it to use for entering text.

UITextField Overview

A brief tour of the class taxonomy should help you understand the capabilities of UITextField and what methods you can expect to use. UITextField's ancestry and related classes are shown in Figure 1. Like any iOS class whose purpose is to display content on-screen, UITextField ultimately inherits from the UIView superclass. However, its immediate superclass is UIControl, because a UITextField object has to interact with the user. Specifically, UITextField issues action messages in response to touches within its view that initiate or end a text editing session.

[Click image to view at full size]
Figure 1.

Since UITextField's input field is actually a view, the class has the usual Objective-C properties that it inherits from UIView. For example, there are properties that define the view's background color, its frame size, the alpha channel value, and its position within its superview. UITextField also adds properties that describe a background image and border style for the view.

Because it displays text, UITextField has properties that specify the text's typeface (or font) and point size, its alignment within the field, its color, and — of course — the text string itself. This last property, text, has read and write characteristics, and we'll use this feature to display and update the table's display of content.

There are also two ancillary protocols that refine UITextField's behavior and action messages in response to user input. Recall that a protocol defines a set of methods that implement specific capabilities for the class. The UITextInputTraits_Protocol defines behaviors that are associated with keyboard input. It can specify the type of virtual keyboard used (such as whether it is standard keyboard or a numeric keypad), and how the text appears on-screen (the text can be displayed as bullet symbols when secure input is requested). It also describes the string displayed in the Return key. For example, "Go" might be presented in the key when an URL is entered, or "Search" if the input string is being used to conduct a search. The UITextFieldDelegate_Protocol specifies the action messages that the UITextField object sends to a delegate object. Many of these messages signal that text editing has begun or has ended on the field. The former messages can be used to invoke a custom keyboard or object (such as a picker) for text entry, while the latter are used to initiate processing on the edited text. Other messages allow access to the edited text, and can modify the behavior of the text display or of the Return key. As an example, the field can clear the text string when it is tapped on, a desirable behavior if you expect the user to enter text that's different from that being displayed.

UITextField's capabilities therefore make it ideal for building an input/editing mechanism into a table. First, the object displays text, so it can be co-opted to display the table's content. Second, the border around the field informs the user that they can tap and change this content. Finally, when the user completes entering new content and taps Return, we can use the resulting action messages to store the revised content into the table's data model. Finally, we send an update message to the table object so that it reads the modified data and updates its on-screen content. So, now let's put what we've learned into writing some code.

Building the Two-Way Table

Let's proceed step-by-step to assemble a working two-way table interface. That means first making a table, then wiring in some instances of UITextField into the table's cells. Then we complete the connection to the data model through the action messages generated by UITextField. For this example, we'll assume a hypothetical table that displays thermostat controls. The table will display days of the weeks as sections. Each day (section) has four intervals that divide the day, with a time specified for each interval, and the heating and cooling values that are used for this interval. See Figure 2 for the planned table design with text fields and their variable names.

[Click image to view at full size]
Figure 2.

Fire up Xcode. Choose File > New Project, and select "Window-based application" for the iOS application template. Name the project "TestTemp." This gives us the bare-minimum code scaffold and a MainWindow.xib file for the app. Like I did in the iOS tables article, I'll use a UITableViewController class to manage both the table display and user responses. So, select File > New File and add a UIViewController subclass with the option for UITableViewController subclass set. Name the file "TableTempControl." Don't set the "With XIB for user interface" option: We'll add any UI objects required into the MainWindow.xib file.

Speaking of that .xib file, open it with Interface Builder (IB) now. In IB, choose Tools > Library to bring up the Library window, then drag and drop UITableViewController class icon into the MainWindow.xib window. This adds the GUI support for the table. Now we have to connect it to the UITableViewController code in the project. This is done by adding the declaration and the property for TableTempControl to the TestTempAppDelegate.h file. Most of the details on how this is done can be found in the article Displaying Tabular Data on iPhones, so I'll skip the details here. Once you are done, you should be able to build and run the project, and see the app present a table. The table presents empty rows for now, since it doesn't have any content to display.

The lack of table content lets us segue into making the class that implements the table's data model. In Xcode, choose File > New and select Objective-C class. Use the default "Subclass of NSObject" setting, and name the class "TableData." Add the code from Listing One into the TableData.h and TableData.m files for this class.

Listing One

#import "TableData.h"

@implementation TableData

// TableData.h
#import <Foundation/Foundation.h>

@interface TableData : NSObject {
	NSMutableArray *timeOfDays;
	NSMutableArray *heatStr;
	NSMutableArray *coolStr;

@property (nonatomic, retain) NSMutableArray *timeOfDays;
@property (nonatomic, retain) NSMutableArray *heatStr;
@property (nonatomic, retain) NSMutableArray *coolStr;

- (void) initTableData;


// TableData.m
@synthesize timeOfDays;
@synthesize heatStr;
@synthesize coolStr;

-(void) initTableData {
	timeOfDays = [[NSMutableArray alloc] init];
	heatStr = [[NSMutableArray alloc] initWithObjects:@"76.0", @"75.0", @"74.0", @"73.0", @"72.0", @"71.0", @"70.0", @"69.0", @"68.0", @"67.0", @"66.0", @"65.0", nil];
	coolStr = [[NSMutableArray alloc] initWithObjects:@"45.0", @"46.0", @"47.0", @"48.0", @"49.0", @"50.0", @"51.0", @"52.0", @"53.0", @"54.0", @"55.0", @"56.0", nil];

-(void)dealloc {
	[coolStr release];
	[heatStr release];
	[timeOfDays release];
	[super dealloc];


From the variable names, you can see that the class stores arrays of data values related to time (timeOfDays), heating (heatStr), and cooling (coolStr). Note also that the arrays are mutable ones, as the data will be subject to change. We have initialized both the heating and cooling arrays with data. We'll deal with the time array later.

Now let's add code to the TableTempControl class to utilize the data model just constructed. Listing Two shows the modifications made to this class's header file.

Listing Two

//  TableTempControl.h

#import <UIKit/UIKit.h>

#import "TableData.h"

@interface TableTempControl : UITableViewController {
	UITableViewCell *settingsCell;
	NSDateFormatter	*dateFormatter;	
	NSMutableArray	*timeOfDays;
	UITextField *timeField;
	UITextField *heatingField;
	UITextField *coolingField;
	TableData *settings;	// Object to store table data
	NSMutableArray *listOfSections; // Content for section headers (titles)
	NSMutableArray *listOfRows; // Content for cell textfields


@property (nonatomic, retain) IBOutlet UITableViewCell *settingsCell;

@property (nonatomic, retain) NSDateFormatter *dateFormatter; 

@property (nonatomic, retain) IBOutlet UITextField *timeField;
@property (nonatomic, retain) IBOutlet UITextField *heatingField;
@property (nonatomic, retain) IBOutlet UITextField *coolingField;


Here, you can see the where UITextField objects are declared. Note that their property definitions include IBOutlet, which makes these objects appear in IB. There's also the declaration for the instance of UITableViewCell, settingsCell, which we'll use to make the table's content display. Another declaration defines the TableData instance for the data model, settings. Finally we declare an NSDateFormatter object to manage the time value. We could use strings to represent the time, but we might as well leverage the capabilities of a ready-made class for displaying dates and time. The arrays listOfSections and listOfRows will contain information about the table's section and rows. Moving on to the TableTempControl.m file, we add the following methods (Listing Three):

Listing Three

- (void) setUpTables {
	NSDictionary *tableSectionsDict;
	NSDictionary *currentWeekdayArrayDict;
	NSDictionary *currentSaturdayArrayDict;
	NSDictionary *currentSundayArrayDict;
	NSArray *sectionNamesArray = [NSArray arrayWithObjects:@"Weekdays", @"Saturday", @"Sunday", nil];
	tableSectionsDict = [NSDictionary dictionaryWithObject:sectionNamesArray forKey:@"Sections"];
	// Add dictionary entry for content for weekdays section (section 0)
	NSArray *currentWeekdayArray = [NSArray arrayWithObjects:@"Morning", @"Day", @"Evening", @"Night", nil];
	currentWeekdayArrayDict = [NSDictionary dictionaryWithObject:currentWeekdayArray forKey:@"Content"];
	// Add dictionary entry for content rows in Saturday section (section 1) -- copy content elements from weekdays array
	NSArray *currentSaturdayArray = [NSArray arrayWithArray:currentWeekdayArray];
	currentSaturdayArrayDict = [NSDictionary dictionaryWithObject:currentSaturdayArray forKey:@"Content"];
	// Add dictionary entry for content rows in Sunday section (section 2)
	NSArray *currentSundayArray = [NSArray arrayWithArray:currentWeekdayArray];
	currentSundayArrayDict = [NSDictionary dictionaryWithObject:currentSundayArray forKey:@"Content"];	
	// Set up lists that determine how the tables appear. This list specifies the section headers.
	[listOfSections addObject:tableSectionsDict];
	// Lists of row content
	[listOfRows addObject:currentWeekdayArrayDict];
	[listOfRows addObject:currentSaturdayArrayDict];
	[listOfRows addObject:currentSundayArrayDict];

// Generate time schedule from schedule data in timeStr. timeStr data must conform to NSDate's short style format.
- (void)setUpSchedule {
	NSArray *timeStr = [[NSArray alloc] initWithObjects:@"1:00 AM", @"2:00 AM", @"3:00 PM", @"4:00 PM", @"6:00 AM", @"7:30 AM", @"1:00 PM", @"8:15 PM", @"5:30 AM", @"9:45 AM", @"4:15 PM", @"11:45 PM", nil];
	// Set up formatter -- no date display, short time format
	dateFormatter = [[NSDateFormatter alloc] init];
	[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
	[dateFormatter setDateStyle:NSDateFormatterNoStyle];
	// Convert time strings into NSDate objects and store in array
	for (int i = 0; i < 12; i++) {
		[settings.timeOfDays addObject:[dateFormatter dateFromString:[timeStr objectAtIndex:i]]];
	} // end for

The setUpTables method generates the header and certain row information, which is stored in NSArray objects. It then builds the structure of arrays that hold the table's content. This arrangement factors the static data (the section headers and content for the cell's textLabel) apart from the dynamic data in the TableData class. The setUpSchedule method uses the NSDateFormatterNoStyle constant to configure the date data to display time values only. Next, in viewDidLoad, you add code to create an instance of the TableData and message its initialization method. Finally, you invoke the setUpTables and setUpSchedule to populate the data model. For the details, see the example program. When you build and run the app, you'll see the table broken into three sections: Weekdays, Saturday, and Sunday. The rows (cells) are empty, though. However, that's expected because there's only stub code for cellForRowAtIndexPath, which is responsible for creating the cell and updating its content. Let's correct that now.

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.