When the user touches an entry in the table, an alert will
appear showing the chosen flower name and color. The final application
will look similar to Figure 1.
Implementation Overview
A table view is created by an instance of UITableView and classes that implement the UITableViewDataSource and UITableViewDelegate
protocols to provide the data that the table will display.
Specifically, the data source needs to supply information on the number
of sections in the table and the number of rows in each section. The
delegate handles creating the cells within the table and reacting to the
user’s selection.
Unlike the picker, where we implemented the required protocols within a standard UIViewController, we’ll be creating an instance of UITableViewController,
which conforms to both of the needed protocols. This simplifies our
development and keeps things as streamlined as possible. In a large
project with complex data needs, you may want to create one or more new
classes specifically for handling the data.
Preparing the Project
Begin by creating a new Xcode project named FlowerColorTable using the Window-Based iPhone Application template.
We will need a new subclass of the UITableViewController class to handle interactions with our table view. To add the new class, complete the following steps:
1. | Create a new file in Xcode (File, New File).
|
2. | Within the New File dialog box, select Cocoa Touch Class, UIViewController subclass, and then check the UITableViewController subclass, as seen in Figure 2. Note that we don’t need a separate XIB file because we’ll be generating the content of table view programmatically.
|
3. | Click Next.
|
4. | Type FlowerColorTableViewController.m as the filename, and be sure that Also Create FlowerColorTableViewController.h is selected.
|
5. | Finally, click the Finish button. The new subclass will be added to your project.
|
The
FlowerColorTableViewController.m will include all the method stubs you
need to get your table up and running quickly. In a few minutes, we’ll
add an instance of this new table view controller subclass to the
MainWindow.xib file. This will create an instance of the FlowerColorTableViewController when the application launches.
Adding Outlets
Before we can make our connections in Interface Builder, we need to create an outlet for the application delegate to access the FlowerColorTableViewController that we’re adding to the system.
Edit FlowerColorTableAppDelegate.h and add a line that imports the FlowerColorTableViewController interface file and an outlet for the instance of FlowerViewController that we will be creating; we’ll call it flowerColorTableViewController for consistency.
Did you Know?
Why do we import the FlowerColorTableViewController.h file? If we didn’t, Xcode wouldn’t “know” what a FlowerColorTableViewController is, and we wouldn’t be able to declare an instance of it.
The FlowerColorTableAppDelegate.h code should read as shown in Listing 1.
Listing 1.
1: #import <UIKit/UIKit.h> 2: #import "FlowerColorTableViewController.h"; 3: 4: @interface FlowerColorTableAppDelegate : NSObject <UIApplicationDelegate> { 5: IBOutlet FlowerColorTableViewController *flowerColorTableViewController; 6: UIWindow *window; 7: } 8: 9: @property (nonatomic, retain) IBOutlet UIWindow *window; 10: 11: @end
|
Lines 2 and 5 are the only additions to the app delegate interface file.
Adding the View
After the view controller has
been instantiated, it will need to add its subview to the application
window. Make these implementation changes within the FlowerColorTableAppDelegate class.
Start by editing application:didFinishLaunchingWithOptions, using the addSubview method to add the add the flowerColorTableViewController’s view as a subview to the window:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch
[window addSubview:flowerColorTableViewController.view];
[window makeKeyAndVisible];
return YES;
}
Next, release the table view controller (flowerColorTableViewController) when the application is finished. Edit the dealloc method to read as follows:
- (void)dealloc {
[flowerColorTableViewController release];
[window release];
[super dealloc];
}
Although we’ve already added references to a table view controller (flowerColorTableViewController) in our code, we haven’t created the object yet. It’s time to open Interface Builder and add an instance of our class.
Adding a Table View and Table View Controller Instance
Double-click the MainWindow.xib
file to open it within Interface Builder. Open the Library, and drag
the Table View Controller icon into the MainWindow.xib file.
Double-click the table view controller within the Interface Builder
Document window (Window, Documents) to show what a sample UITable looks like in Interface Builder, as shown in Figure 3.
Okay, so we’ve added an instance of UITableViewController to the project, but that’s not quite what we need! The FlowerColorTableViewController class is our subclass of UITableViewController, so that’s what we want to instantiate and use in the application.
By the Way
The UITableViewController instance that you just added includes an instance of a table view (UITableView), so this is the only object needed in Interface Builder.
Select the Table View
Controller icon in the XIB file, and open the Identity Inspector
(Command+4). Edit the class identity to read FlowerColorTableViewController, as shown in Figure 4.
The
application will now instantiate our table view controller when it
launches, but the controller still isn’t connected to anything. To
connect to the flowerColorTableViewController
outlet created earlier, Control-drag from Flower Color Table App
Delegate to the Flower Color Table View Controller icon. When the
Outlets pop-up window appears, choose flowerColorTableViewController (see Figure 5).
All the connections are in
place for the table view controller and a table view. Switch back to
Xcode, and click Build and Go to test the application. An empty table
will appear. It’s empty, but it’s a table! Next step? Data!
Providing Data to the Table View
With all the structural work
out of the way, the table is ready to display something. As mentioned
earlier, implementing the required methods for the UITableViewDataSource and UITableViewDelegate
protocols is very similar to creating a picker. For this example, we
create a table that lists flowers divided into sections by color.
Creating Sample Data
To keep things simple, we’ll only consider two colors: red and blue. We’ll populate two arrays (redFlowers, blueFlowers) with a few appropriate flower names.
Begin by updating FlowerColorTableViewController.h to include the two NSMutableArrays we’ll be using:
@interface FlowerColorTableViewController : UITableViewController {
NSMutableArray *redFlowers;
NSMutableArray *blueFlowers;
}
Turning to the implementation in FlowerColorTableViewController.m, find the viewDidLoad
method and uncomment it. We will implement this method so that we have a
convenient place to populate the arrays. Add the code in Listing 2 to initialize of the redFlowers and blueFlowers arrays with several flowers in each.
Listing 2.
- (void)viewDidLoad { [super viewDidLoad]; redFlowers = [[NSMutableArray alloc] initWithObjects:@"Gerbera",@"Peony",@"Rose" ,@"Poppy",@"Tulip",@"Anthurium",@"Anemone",nil]; blueFlowers = [[NSMutableArray alloc] initWithObjects:@"Hyacinth",@"Hydrangea" ,@"Sea Holly",@"Phlox",@"Iris",@"Bluebell" ,@"Cyanus",nil]; }
|
As always, make sure that you release the two arrays when finished. Edit the dealloc method to read as follows:
- (void)dealloc {
[redFlowers release];
[blueFlowers release];
[super dealloc];
}
As a last step, add
constants that we can use to refer to our color sections. At the top of
FlowerColorTableViewController.m, enter the following constant
definitions:
#define sectionCount 2
#define redSection 0
#define blueSection 1
The first constant, sectionCount,
is the number of sections that will be displayed in the table. Because
we’re implementing red and blue flower lists, this value is 2. The next constant, redSection, denotes that the listing of red flowers in the table will be shown first (section 0), while the third and final constant, blueSection, identifies that the blue section of flowers will appear second (section 1).
Implementing the Table View Controller Data Source Methods
Our application now has the
data it needs to create a table, but it doesn’t yet “understand” how to
get that data into the table view itself. Thankfully, the methods that a
table requires to display information are easy to understand and, more
important, easy to implement. Because this example includes sections
(red and blue), we need to include these three methods in
FlowerColorViewController.m as part of the UITableViewDataSource protocol:
numberofSectionsInTableView: Returns the number of sections within a given table
tableView:tableViewnumberOfRowsInSection: Returns the number of rows in a section
tableView:titleForHeaderInSection: Returns a string to be used as the title for a given section number
The number of sections has already been defined in the constant sectionCount,
so implementing the first method requires nothing more than returning
this constant. Add this code to FlowerColorViewController.m:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return sectionCount;
}
The second method requires us to
return the number of rows (cells) that will be displayed in a given
section. Because the rows in each section will be filled with the
strings in the redFlowers and blueFlowers arrays, we can return the count of elements in each array using the array count method.
Use a switch statement along with the redSection and blueSection
constants that were defined earlier to return the appropriate counts
for each of the two arrays. The final implementation is shown in Listing 3. Be sure to add this to FlowerColorViewController.m.
Listing 3.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { switch (section) { case redSection: return [redFlowers count]; case blueSection: return [blueFlowers count]; default: return 0; } }
|
By the Way
Even though it is
impossible for our application to reach a section other than red or
blue, it is still good practice to provide a default case to the switch
statement. This ensures that even if we haven’t properly identified all
of our potential cases, it will still be caught by the default case.
For the third data source method, tableView:titleForHeaderInSection,
you can turn again to the defined constants and a switch statement to
very easily return an appropriate string for a given section number.
Implement the method as shown in Listing 4.
Listing 4.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { switch (section) { case redSection: return @"Red"; case blueSection: return @"Blue"; default: return @"Unknown"; } }
|
That wraps up what needs to be provided to satisfy the UITableViewDataSource protocol. However, as you’ve seen, these methods don’t provide the actual data that will be visible in the table cells.
Populating the Cells
At long last, we’ve reached the method that actually makes our table display something! Both tableView:cellForRowAtIndexPath
will do the “heavy lifting” in the application. This single method,
which needs to implemented within your table view controller, returns a
cell (UITableViewCell) object for a given table section and row.
By the Way
The methods required for working with table views frequently use an NSIndexPath object to communicate row and section information. When dealing with an incoming NSIndexPath object in your table methods, you can use the accessors IndexPath.section and IndexPath.row to get to the current section and row.
To implement the tableView:cellForRowAtIndexPath method properly, we must create and return a properly formatted UITableViewCell.
What makes this process
interesting is that as cells move on and off the screen, we don’t want
to keep releasing and reallocating memory. We also don’t want to
allocate memory for every single cell that the table could display. So,
what is the alternative? To reuse cells that are no longer needed to
generate the current display. The good news is that Apple has already
set up methods and a process for this to occur automatically.
Take a close look at the method stub for tableView:cellForRowIndexPath; specifically, this snippet:
UITableViewCell *cell = (UITableViewCell*)[tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc]
initWithFrame:CGRectZero
reuseIdentifier:CellIdentifier] autorelease];
}
This code attempts to use the dequeueReusableCellWithIdentifier UITableView
method to find a cell that has already been allocated but that is ready
to be reused. In the event that an appropriate cell can’t be found
(such as the first time the table view loads its cells), a new cell is
allocated and initialized. You shouldn’t have any reason to change this
prewritten logic for most table-based applications.
After a cell object has been appropriately allocated, the method must format the cell for the indexPath object provided. In other words, we must make sure that for whatever section is specified in indexPath.section and whatever row is passed in indexPath.row, the cell object is given the necessary label.
To set a cell’s label to a given string, first use textLabel to return the UILabel object for the cell, then the setText method to update the label. For example:
[[cell textLabel]setText: @"My Cell Label"]
Because we don’t want to set a
static string for the cell labels and our labels are stored in arrays,
we need to retrieve the appropriate label string from the array, and
then pass it to setText. Remember that the individual cell row that we need to return will be provided by indexPath.row, so we can use that to index into our array. To retrieve the current text label for a member of the redFlowers array, we can use the following:
[redFlowers objectAtIndex:indexPath.row]
These two lines can be combined into a single statement that sets the cell label text to the current row of the redFlowers array:
[[cell textLabel] setText:[redFlowers objectAtIndex:indexPath.row]]
This is good, but not quite the solution to all our problems. We need to account for both the redFlowers and blueFlowers arrays and display each within the appropriate section. Once again, we’ll turn to the switch statement to make this happen, this time using indexPath.section to determine whether the cell should be set to a member of the redFlowers array or the blueFlowers array.
Your final code, shown in Listing 13.5, should be an addition to the existing tableView:cellForRowAtIndexPath method stub.
Listing 5.
1: - (UITableViewCell *)tableView:(UITableView *)tableView 2: cellForRowAtIndexPath:(NSIndexPath *)indexPath 3: { 4: static NSString *CellIdentifier = @"Cell"; 5: 6: UITableViewCell *cell = (UITableViewCell*)[tableView 7: dequeueReusableCellWithIdentifier:CellIdentifier]; 8: if(cell == nil) 9: { 10: cell = [[[UITableViewCell alloc] 11: initWithFrame:CGRectZero 12: reuseIdentifier:CellIdentifier] autorelease]; 13: } 14: 15: switch (indexPath.section) { 16: case redSection: 17: [[cell textLabel] 18: setText:[redFlowers objectAtIndex:indexPath.row]]; 19: break; 20: case blueSection: 21: [[cell textLabel] 22: setText:[blueFlowers objectAtIndex:indexPath.row]]; 23: break; 24: default: 25: [[cell textLabel] 26: setText:@"Unknown"]; 27: } 28: return cell; 29: }
|
The
moment you’ve been waiting for has arrived! You should now be able to
launch your application and view the result. Congratulations, you’ve
just implemented a table view from scratch!
Reacting to a Row Touch Event
A table that displays
information is all fine and dandy, but it would be nice if the user had a
means of interacting with it. Unlike other UI elements where we’d need
to define an action and make connections in Interface Builders, we can
add some basic interactivity to the FlowerColorTable application by
implementing a method from the UITableViewDelegate: tableView:didSelectRowAtIndexPath.
This method is called when a table row has been touched by the user.
The key to identifying the specific row and section that was selected is
indexPath, an instance of NSIndexPath.
How you react to a row selection event is up to you. For the sake of this example, we’re going to use UIAlertView to display a message. The implementation, shown in Listing 6, should look very familiar by this point. Add this delegate method to the FlowerColorTableViewController.m file:
Listing 6.
1: - ( void)tableView:( UITableView *)tableView 2: didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 3: UIAlertView *showSelection; 4: NSString *flowerMessage; 5: switch (indexPath.section) { 6: case redSection: 7: flowerMessage=[[NSString alloc] 8: initWithFormat: 9: @"You chose the red flower - %@", 10: [redFlowers objectAtIndex: indexPath.row]]; 11: break; 12: case blueSection: 13: flowerMessage=[[NSString alloc] 14: initWithFormat: 15: @"You chose the blue flower - %@", 16: [blueFlowers objectAtIndex: indexPath.row]]; 17: break; 18: default: 19: flowerMessage=[[NSString alloc] 20: initWithFormat: 21: @"I have no idea what you chose!?"]; 22: break; 23: } 24: 25: showSelection = [[UIAlertView alloc] 26: initWithTitle: @"Flower Selected" 27: message:flowerMessage 28: delegate: nil 29: cancelButtonTitle: @"Ok" 30: otherButtonTitles: nil]; 31: [showSelection show]; 32: [showSelection release]; 33: [flowerMessage release]; 34: }
|
Lines 3–4 declare flowerMessage and showSelection variables that will be used for the message string shown to the user and the UIAlertView instance that will display the message, respectively.
Lines 5–23 use a switch statement with indexPath.section to determine which flower array our selection comes from and the indexPath.row value to identify the specific element of the array that was chosen. A string (flowerMessage) is allocated and formatted to contain the value of the selection.
Lines 25–31 create and display an alert view instance (showSelection) containing the message string (flowerMessage).
Lines 32–33 release the instance of the alert view and the message string.
After implementing
this function, build and test the application again. Touch a row, and
review the result. The application will now display an alert box with
the results of your selection, as shown in Figure 6.