iOS Reference Library Apple Developer
Search

Creating and Configuring a Table View

Your application must present a table view to users before it can manage it in response to taps on rows and other actions. This chapter shows what you must do to create a table view, configure it, and populate it with data.

Most of the code examples shown in this chapter come from the example projects TableViewSuite and TheElements.

Basics of Table View Creation

The creation of a table view requires the interaction of several entities in an application: the client, the table view itself, and the table view’s data source and delegate. The client, delegate, and data source are often the same object, but can be separate objects. The client starts the calling sequence, diagrammed in Figure 4-1.

  1. The client creates a UITableView instance in a certain frame and style. It can do this either programmatically or in Interface Builder. The frame is usually set to the screen frame, minus the height of the status bar or, in a navigation-based application, to the screen frame minus the heights of the status bar and the navigation bar. The client may also set global properties of the table view at this point, such as its autoresizing behavior or a global row height.

    See “Creating a Table View Application the Easy Way” and “Creating a Table View Programmatically” for information on creating table views using Interface Builder and programmatically, respectively.

  2. The client sets the data source and delegate of the table view and sends reloadData to it. The data source must adopt the UITableViewDataSource protocol and the delegate must adopt the UITableViewDelegate protocol.

  3. The data source receives a numberOfSectionsInTableView: message from the UITableView object and returns the number of sections in the table view. Although this is an optional protocol method, the data source must implement it if the table view has more than one section.

  4. For each section, the data source receives a tableView:numberOfRowsInSection: message and responds by returning the number of rows for the section.

  5. The data source receives a tableView:cellForRowAtIndexPath: message for each visible row in the table view. It responds by configuring and returning a UITableViewCell object for each row. The UITableView object uses this cell to draw the row.

Figure 4-1  Calling sequence for creating and configuring a table view

Calling sequence for creating and configuring a table view

The diagram in Figure 4-1 shows the required protocol methods as well as the numberOfSectionsInTableView: method. (Note that zero is a valid value to return for number of sections.) Populating the table view with data occurs in steps 3 through 5; “Populating the Table View With Data” describes how you implement the methods mentioned in these steps.

The data source and the delegate may implement other optional methods of their protocols to further configure the table view. For example, the data source might want to provide titles for each of the sections in the table view by implementing tableView:titleForHeaderInSection:. “Optional Table-View Configurations” describes some of these optional table-view customizations.

You create a table view in either the plain style (UITableViewStylePlain) or the grouped style (UITableViewStyleGrouped). (You specify the style when you initialize the table view by calling, directly or indirectly, the initWithFrame:style: method. Although, the procedure for creating a table view in either styles is identical, you may want to perform different kinds of configurations. For example, because a grouped table view generally presents item detail, you may also want to add custom accessory views (for example, switches and sliders) or custom content (for example, text fields) to cells in the delegate’s tableView:cellForRowAtIndexPath: method; “A Closer Look at Table-View Cells” gives an example of this.

Recommendations for Creating and Configuring Table Views

There are many ways to put together a table-view application. For example, you could use an instance of a custom NSObject subclass to create, configure, and manage a table view. However, you will find the task much easier if you adopt the classes, techniques, and design patterns that the UIKit offers for this purpose. The following approaches are recommended:

If the view to be managed is a composite view in which a table view is one of multiple subviews, you must use a custom subclass of UIViewController to manage the table view (and other views). Do not use a UITableViewController object because this controller class sizes the table view to fill the screen between the navigation bar and the tab bar (if either are present).

Creating a Table View Application the Easy Way

Creating a table-view application in Xcode is very simple. When you create your project you select a template with stub code and nib files that supply the structure for setting up and managing table views. To create an application that is structured around table views, complete the following steps:

  1. In Xcode, choose New Project from the File menu.

  2. Select the Navigation-based Application template project and click Choose.

  3. Specify a name and location for the project and click Save.

Xcode creates the table view in a nib file and creates an instance of a custom subclass of UITableViewController that, at runtime, loads the table view from the nib file, populates it, and manages it.

Examining the Project: How Things are Set Up

Before you begin any coding or nib work, it’s useful to examine the key components of the project you just created: the application-delegate and root view-controller classes and the nib files MainWindow.xib and RootViewController.xib. Figure 4-2 shows where these items are located in the Groups & Files view of the Xcode project window.

Figure 4-2  A table-view application project when just created

Double-click MainWindow.xib to open this nib file in Interface Builder. In the document window for this nib file you can see the objects it contains (shown in Figure 4-3). The main top-level objects are File's Owner (a proxy for the application object itself), the application delegate, the application’s window, and a UINavigationController object. The last object is the root of an object graph that is significant for the table view. The direct children of the navigation controller are the navigation bar that runs across the screen above the table view and a placeholder for an instance of the RootViewController class of the project. Because it is a child of the navigation controller, this view controller is made the root view controller, the first one on the stack of view controllers managed by the navigation controller (explained in “Navigating a Data Hierarchy With Table Views”).

Figure 4-3  The contents of the main window‚Äôs nib file

When the application is launched, the MainWindow.xib nib file is loaded into memory. The application delegate displays the initial user interface in the two lines of code in Listing 4-1. By asking the navigation controller for its view, the application delegate obtains the navigation bar, the title in the navigation bar (the navigation item), and the table view associated with the root view controller.

Listing 4-1  The application delegate displaying the initial user interface

- (void)applicationDidFinishLaunching:(UIApplication *)application {
 
    [window addSubview:[navigationController view]];
    [window makeKeyAndVisible];
}

But (you may have noticed) the Root View Controller object in the nib document window in Figure 4-3 does not show a subordinate table view. Where does the table view come from?

Select the Root View Controller object in the document window and display the attributes inspector for that object (Command-1). Here you see that the Nib Name field is set to RootViewController (as shown in Figure 4-4). When the delegate asks the navigation controller for its views, the navigation controller asks its root view controller for its view, and the root view controller loads the nib file specified through this attribute.

Figure 4-4  Setting the nib-name property of the table-view controller

The RootViewController.xib nib file contains the table view and sets the RootViewController object to be File’s Owner. To see the connections between RootViewController and its table view, right-click (or Control-click) File’s Owner. This action displays a heads-up display as depicted in Figure 4-5.

Figure 4-5  Connections in the root view controller‚Äôs nib file

The RootViewController object keeps two references to the table view (one is via the view property inherited from UIViewController); it is also set to be the data source and delegate of the table view.

You can also change the characteristics of the table view by selecting it in the nib document window and then going to the Attributes pane of the Interface Builder inspector. For example, you could change the style of the table view from plain (the default) to grouped.

The table view managed by RootViewController is empty at runtime until the the table-view controller populates it with data; see “Populating the Table View With Data” to learn the procedure for doing this. You can also programmatically configure the table view or the navigation bar, such as specifying the title of the navigation bar or the title of the table view. (Common sites for this configuration code are the initWithStyle: or viewDidLoad methods.) “Optional Table-View Configurations” discusses some of the things you can do to customize the appearance or behavior of a table view.

Adding Table Views to the Application

If your application displays and manages more than one table view, you need to add those table views to your project. You typically add a table view by adding a UITableViewController object, which creates the table view it manages. After adding a custom table-view controller class to your project, you can configure the table view programmatically; or you can configure it and any related views in a nib file that is loaded at runtime. For situations where a table view is the sole view, the programmatic approach is easier. The nib-file approach is appropriate when the managed view is more complex—for example, a view with a table view as only one of its subviews.

To add a custom table-view controller class, open your Xcode project and choose New File from the File menu. This command displays the New File window, as shown in Figure 4-6. Select Cocoa Touch Class in the left column and then select the “UIViewController subclass” image. Finally, select the “UITableViewController subclass” check box and click Next. In the subsequent window, supply a suitable name for the header and implementation files of your custom class and click Finish.

Figure 4-6  Specifying a custom subclass of UITableViewController

As with the RootViewController class that comes with the Navigation-based Application template, you must implement the methods of the custom table-view controller class that populate the table view with data. You may also programmatically set properties of the table view or the navigation bar (typically in the initWithStyle: or viewDidLoad methods). When users select an item in the table view managed by the RootViewController class, you allocate and initialize an instance of the second table-view controller and push it onto the stack managed by the application’s navigation controller; “Managing Selections” describes this procedure in detail.

Note: Populating a table view with data and configuring a table view are discussed in ‚ÄúPopulating the Table View With Data‚Äù and ‚ÄúOptional Table-View Configurations,‚Äù respectively.

If you prefer to load the table view managed by a custom table-view controller from a nib file, you must do the following:

  1. In Interface Builder, create an empty Cocoa Touch nib file (File > New).

  2. Drag a UITableViewController object from the Interface Builder Library into the nib document window.

  3. Save the nib file in your project directory under an appropriate name and, when prompted, select your project to have the nib file added to it.

  4. Select Table View Controller in the nib document window and open the Identity pane of the inspector. Set the class to your custom table-view controller class.

  5. Select File’s Owner in the nib document window and set its class identity to the custom table-view controller class.

  6. Customize the table view in Interface Builder.

  7. Select the table-view controller in the nib document window, open the Attributes pane of the inspector, and enter (or select) the name of the nib file in the Nib Name field.

When at runtime the current table-view controller allocates and initializes an instance of the new table-view controller, the associated nib file is loaded.

Creating a Table View Programmatically

If you choose not to use UITableViewController for table-view management, you must replicate what this class gives you “for free.” The class creating the table view (a UIViewController subclass in the examples below) typically makes itself the data source and delegate by adopting the UITableViewDataSource and UITableViewDelegate protocols. The adoption syntax appears just after the superclass in the @interface directive, as shown in “Creating a Table View Programmatically.”

Listing 4-2  Adopting the data source and delegate protocols

@interface RootViewController : UIViewController  <UITableViewDelegate, UITableViewDataSource> {
    NSArray *timeZoneNames;
}
 
@property (nonatomic, retain) NSArray *timeZoneNames;
@end

The next step is for the client to allocate and initialize an instance of the UITableView class. “Creating a Table View Application the Easy Way” gives an example of a client that creates a UITableView object in the plain style, specifies its autoresizing characteristics, and then sets itself to be both data source and delegate. Again, keep in mind that the UITableViewController does all of this for you automatically.

Listing 4-3  Creating a table view

- (void)loadView
{
    UITableView *tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]
                                  style:UITableViewStylePlain];
    tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
    tableView.delegate = self;
    tableView.dataSource = self;
    [tableView reloadData];
 
    self.view = tableView;
    [tableView release];
}

Because in this example the class creating the table view is a subclass of UIViewController, it assigns the created table view to its view property, which it inherits from that class. It also sends a reloadData message to the table view, causing the table view to initiate the procedure for populating its sections and rows with data.

Populating the Table View With Data

Just after it is created, a table-view object receives a reloadData message, which tells it to start querying the data source and delegate for the information it needs for the sections and rows it displays. The table view immediately asks the data source for its logical dimensions—that is, the number of sections and the number of rows in each section. It then repeatedly invokes the tableView:cellForRowAtIndexPath: to get a cell object for each visible row; it uses this UITableViewCell object to draw the content of the row. (Scrolling a table view also causes an invocation of tableView:cellForRowAtIndexPath: for each newly visible row.)

“Populating the Table View With Data” shows how the data source and the delegate implement the required protocol methods for configuring a table view.

Listing 4-4  Populating a table view with data

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [regions count];
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // Number of rows is the number of time zones in the region for the specified section.
    Region *region = [regions objectAtIndex:section];
    return [region.timeZoneWrappers count];
}
 
 
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    // The header for the section is the region name -- get this from the region at the section index.
    Region *region = [regions objectAtIndex:section];
    return [region name];
}
 
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *MyIdentifier = @"MyIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
    }
    Region *region = [regions objectAtIndex:indexPath.section];
    TimeZoneWrapper *timeZoneWrapper = [region.timeZoneWrappers objectAtIndex:indexPath.row];
    cell.textLabel.text = timeZoneWrapper.localeName;
    return cell;
}

The data source in its implementation of the tableView:cellForRowAtIndexPath: method returns a configured cell object that the table view can use to draw a row. For performance reasons, the data source tries to reuse cells as much as possible. It first asks the table view for a specific reusable cell object by sending it dequeueReusableCellWithIdentifier:. message. if no such object exists, the data source creates it, assigning it a reuse identifier. It sets the cell’s content (its text in this example) and returns it. “A Closer Look at Table-View Cells” discusses this data-source method and UITableViewCell objects in more detail.

The implementation of tableView:cellForRowAtIndexPath: in Listing 4-4 illustrates an aspect of the table view API that you’ll frequently encounter. The method includes an NSIndexPath argument that identifies the table-view section and row for the cell that the data source is to provide. (In this case, the section index is not needed because the table view has only one section.) UIKit declares a category of the NSIndexPath class, which is defined in the Foundation framework. This category extends the class to enable the identification of table-view rows by section and row index numbers. For information on this category, see NSIndexPath UIKit Additions.

Populating an Indexed List

An indexed list (also known as a section index table view) is ideally suited for navigating large amounts of data organized by a conventional ordering scheme such as an alphabet. An indexed list is a table view in the plain style that is specially configured through three UITableViewDataSource methods: sectionIndexTitlesForTableView:, tableView:titleForHeaderInSection:, and tableView:sectionForSectionIndexTitle:atIndex:. The first method returns an array of the strings to use as the index entries (in order), the second method maps these index strings to the titles of the table-view’s sections (they don’t have to be the same), and the third method returns the section index related to the entry the user tapped in the index.

The data that that you use to populate an indexed list should be organized to reflect this indexing model. Specifically, you need to build an array of arrays. Each inner array corresponds to a section in the table; section arrays are sorted (or collated) within the outer array according to the prevailing ordering scheme, which is often an alphabetical scheme (for example, A through Z). Additionally, the items in each section array are sorted. You could build and sort this array of arrays yourself, but fortunately the UILocalizedIndexedCollation class makes the tasks of building and sorting these data structures and providing data to the table view much easier. The class also collates items in the arrays according to the current localization.

Note: The UILocalizedIndexedCollation class was added to the UIKit framework in iOS 3.0.

However you internally manage this array-of-arrays structure is up to you. The objects to be collated should have a property or method that returns a string value that the UILocalizedIndexedCollation class uses in collation; if it is a method, it should have no parameters. You might find it convenient to define a custom model class whose instances represent the rows in the table view. These model objects not only return a string value but define a property that holds the index of the section array to which the object is assigned. Listing 4-5 illustrates the definition of a class that declares a name property for the former purpose and a sectionNumber property for the latter purpose.

Listing 4-5  Defining the model-object interface

@interface State : NSObject {
    NSString *name;
    NSString *capitol;
    NSString *population;
    NSInteger sectionNumber;
}
 
@property(nonatomic,copy) NSString *name;
@property(nonatomic,copy) NSString *capitol;
@property(nonatomic,copy) NSString *population;
@property NSInteger sectionNumber;
@end

Before your table-view controller is asked to populate the table view, load the data to be used from whatever source and create instances of your model class from this data. The example in Listing 4-6 loads data defined in a property list and creates the model objects from that. It also obtains the shared instance of UILocalizedIndexedCollation and initializes the mutable array (states) that will contain the section arrays.

Listing 4-6  Loading the table-view data and initializing the model objects

- (void)viewDidLoad {
    [super viewDidLoad];
    UILocalizedIndexedCollation *theCollation = [UILocalizedIndexedCollation currentCollation];
    self.states = [NSMutableArray arrayWithCapacity:1];
 
    NSString *thePath = [[NSBundle mainBundle] pathForResource:@"States" ofType:@"plist"];
    NSArray *tempArray;
    NSMutableArray *statesTemp;
    if (thePath && (tempArray = [NSArray arrayWithContentsOfFile:thePath]) ) {
        statesTemp = [NSMutableArray arrayWithCapacity:1];
        for (NSDictionary *stateDict in tempArray) {
            State *aState = [[State alloc] init];
            aState.name = [stateDict objectForKey:@"Name"];
            aState.population = [stateDict objectForKey:@"Population"];
            aState.capitol = [stateDict objectForKey:@"Capitol"];
            [statesTemp addObject:aState];
            [aState release];
        }
    } else  {
        return;
    }

Once the data source has this “raw” array of model objects, it can process it with the facilities of the UILocalizedIndexedCollation class. The code snippet in Listing 4-7 marks the significant parts with numbers.

Listing 4-7  Preparing the data for the indexed list

    // viewDidLoad continued...
    // (1)
    for (State *theState in statesTemp) {
        NSInteger sect = [theCollation sectionForObject:theState collationStringSelector:@selector(name)];
        theState.sectionNumber = sect;
    }
    // (2)
    NSInteger highSection = [[theCollation sectionTitles] count];
    NSMutableArray *sectionArrays = [NSMutableArray arrayWithCapacity:highSection];
    for (int i=0; i<=highSection; i++) {
        NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:1];
        [sectionArrays addObject:sectionArray];
    }
    // (3)
    for (State *theState in statesTemp) {
        [(NSMutableArray *)[sectionArrays objectAtIndex:theState.sectionNumber] addObject:theState];
    }
    // (4)
    for (NSMutableArray *sectionArray in sectionArrays) {
        NSArray *sortedSection = [theCollation sortedArrayFromArray:sectionArray
            collationStringSelector:@selector(name)];
        [self.states addObject:sortedSection];
    }
} // end of viewDidLoad
  1. The data source enumerates the array of model objects and sends sectionForObject:collationStringSelector: to the collation manager on each iteration. This method takes as arguments a model object and a property or method of the object that it uses in collation. Each call returns the index of the section array to which the model object belongs, and that value is assigned to the sectionNumber property.

  2. The data source source then creates a (temporary) outer mutable array and mutable arrays for each section; it adds each created section array to the outer array.

  3. It then enumerates the array of model objects and adds each object to its assigned section array.

  4. The data source enumerates the array of section arrays and calls sortedArrayFromArray:collationStringSelector: on the collation manager to sort the items in each array. It passes in a section array and a property or method that is to be used in sorting the items in the array. Each sorted section array is added to the final outer array.

Now the data source is ready to populate its table view with data. It implements the methods specific to indexed lists as shown in Listing 4-8. In doing this it calls two UILocalizedIndexedCollation methods: sectionIndexTitles and sectionForSectionIndexTitleAtIndex:. Also note that in tableView:titleForHeaderInSection: it suppresses any headers from appearing in the table view when the associated section does not have any items.

Listing 4-8  Providing section-index data to the table view

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
}
 
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if ([[self.states objectAtIndex:section] count] > 0) {
        return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section];
    }
    return nil;
}
 
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index];
}

Finally, the data source should implement the UITableViewDataSource methods that are common to all table views. Listing 4-9 gives examples of these implementations, and illustrates how to use the section and row properties of the table view–specific category of the NSIndexPath class described in NSIndexPath UIKit Additions.

Listing 4-9  Populating the rows of an indexed list

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.states count];
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [[self.states objectAtIndex:section] count];
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"StateCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                   reuseIdentifier:CellIdentifier] autorelease];
    }
    State *stateObj = [[self.states objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
    cell.textLabel.text = stateObj.name;
    return cell;
}

After initially populating the table view following the procedure outlined above, you can thereafter reload the contents of the index by calling reloadSectionIndexTitles, a method introduced in iOS 3.0.

For table views that are indexed lists, when the delegate assigns cells for rows in tableView:cellForRowAtIndexPath:, it should ensure that the accessoryType property of the cell is set to UITableViewCellAccessoryNone. You can force a redisplay of the section titles of an indexed list by calling the reloadSectionIndexTitles method (available in iOS 3.0).

Optional Table-View Configurations

The table view API allows you to configure various visual and behavioral aspects of a table view, including specific rows and sections. The following examples serve to give you some idea of the options available to you.

In the same block of code that creates the table view , you can apply global configurations using certain methods of the UITableView class. The code example in Listing 4-10 adds a custom title for the table view (using a UILabel object).

Listing 4-10  Adding a title to the table view

- (void)loadView
{
    [super loadView];
 
    CGRect titleRect = CGRectMake(0, 0, 300, 40);
    UILabel *tableTitle = [[UILabel alloc] initWithFrame:titleRect];
    tableTitle.textColor = [UIColor blueColor];
    tableTitle.backgroundColor = [self.tableView backgroundColor];
    tableTitle.opaque = YES;
    tableTitle.font = [UIFont boldSystemFontOfSize:18];
    tableTitle.text = [curTrail objectForKey:@"Name"];
    self.tableView.tableHeaderView = tableTitle;
    [self.tableView reloadData];
    [tableTitle release];
}

The example in Listing 4-11 returns a title string for a section header.

Listing 4-11  Returning a header title for a specific section

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    // Returns section title based on physical state: [solid, liquid, gas, artificial]
    return [[[PeriodicElements sharedPeriodicElements] elementPhysicalStatesArray] objectAtIndex:section];
}

The code in Listing 4-12 moves a specific row to the next level of indentation.

Listing 4-12  Custom indentation of a row

- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
    if ( indexPath.section==TRAIL_MAP_SECTION && indexPath.row==0 ) {
        return 2;
    }
    return 1;
}

The example in Listing 4-13 varies the height of a specific row based on its index value.

Listing 4-13  Varying row height

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat result;
 
    switch ([indexPath row])
    {
        case 0:
        {
            result = kUIRowHeight;
            break;
        }
        case 1:
        {
            result = kUIRowLabelHeight;
            break;
        }
    }
    return result;
}

You can also affect the appearance of rows by returning custom UITableViewCell objects with specially formatted subviews for content in tableView:cellForRowAtIndexPath:. “A Closer Look at Table-View Cells” discusses cell customization.




Last updated: 2010-08-03

Did this document help you? Yes It's good, but... Not helpful...