4.1. Populating a Table View with Data

Problem

You would like to populate your table view with data.

Solution

Conform to the UITableViewDataSource protocol in an object and assign that object to the dataSource property of a table view.

Discussion

Create an object that conforms to the UITableViewDataSource protocol and assign it to a table view instance. Then, by responding to the data source messages, provide information to your table view. For this example, let’s go ahead and declare the .m file of our view controller, which will later create a table view on its own view, in code:

#import "ViewController.h"

static NSString *TableViewCellIdentifier = @"MyCells";

@interface ViewController () <UITableViewDataSource>
@property (nonatomic, strong) UITableView *myTableView;
@end

The TableViewCellIdentifier contains our cell identifiers as a static string variable. Each cell, as you will learn soon, can have an identifier, which is great for reusing cells. For now, think about this as a unique identifier for all the cells in our table view, nothing more.

In the viewDidLoad method of our view controller, we create the table view and assign our view controller as its data source:

- (void)viewDidLoad{
    [super viewDidLoad];

    self.myTableView =
    [[UITableView alloc] initWithFrame:self.view.bounds
                                 style:UITableViewStylePlain];

    [self.myTableView registerClass:[UITableViewCell class]
             forCellReuseIdentifier:TableViewCellIdentifier];

    self.myTableView.dataSource = self;

    /* Make sure our table view resizes correctly */
    self.myTableView.autoresizingMask =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleHeight;

    [self.view addSubview:self.myTableView];

}

Everything is very simple in this code snippet except for the registerClass:forCellReuseIdentifier: method that we are calling on the instance of our table view. What does this method do, you ask? The registerClass parameter of this method simply takes a class name that denotes the type of object that you want your table view to load when it renders each cell. Cells inside a table view all have to be direct or indirect ancestors of the UITableViewCell class. This class on its own provides a lot of functionalities to programmers, but if you want to extend this class, you can simply subclass it and add your new functionalities to your own class. So going back to the registerClass parameter of the aforementioned method, you have to pass the class name of your cells to this parameter and then pass an identifier to the forCellReuseIdentifier parameter. The reason behind associating table view cell classes with identifiers is that later, when you populate your table view, you can simply pass the same identifier to the table view’s dequeueReusableCellWithIdentifier:forIndexPath: method and have the table view instantiate the cell for you if one cannot be reused. This is great stuff, because in previous versions of the iOS SDK, programmers had to instantiate these cells themselves if a previous and reusable cell could not be retrieved from the table view.

Now we need to make sure our table view responds to the @required methods of the UITableViewDataSource protocol. Press the Command+Shift+O key combination on your keyboard, type this protocol name in the dialog, and then press the Enter key. This will show you the required methods for this protocol.

The UITableView class defines a property called dataSource. This is an untyped object that must conform to the UITableViewDataSource protocol. Every time a table view is refreshed and reloaded using the reloadData method, the table view will call various methods in its data source to find out about the data you intend to populate it with. A table view data source can implement three important methods, two of which are mandatory for every data source:

numberOfSectionsInTableView:

This method allows the data source to inform the table view of the number of sections that must be loaded into the table.

tableView:numberOfRowsInSection:

This method tells the view controller how many cells or rows have to be loaded for each section. The section number is passed to the data source in the numberOfRowsInSection parameter. The implementation of this method is mandatory in the data source object.

tableView:cellForRowAtIndexPath:

This method is responsible for returning instances of the UITableViewCell class as rows that have to be populated into the table view. The implementation of this method is mandatory in the data source object.

So let’s go ahead and implement these methods in our view controller, one by one. First, let’s tell the table view that we want it to render three sections:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

    if ([tableView isEqual:self.myTableView]){
        return 3;
    }

    return 0;

}

Then we tell the table view how many rows we want it to render for each section:

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section{

    if ([tableView isEqual:self.myTableView]){
        switch (section){
            case 0:{
                return 3;
                break;
            }
            case 1:{
                return 5;
                break;
            }
            case 2:{
                return 8;
                break;
            }
        }
    }
    return 0;
}

So up to now, we have asked the table view to render three sections with three rows in the first, five rows in the second, and eight rows in the third section. What’s next? We have to return instances of UITableViewCell to the table view—the cells that we want the table view to render:

- (UITableViewCell *)     tableView:(UITableView *)tableView
              cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    UITableViewCell *cell = nil;

    if ([tableView isEqual:self.myTableView]){

        cell = [tableView
                  dequeueReusableCellWithIdentifier:TableViewCellIdentifier
                  forIndexPath:indexPath];

        cell.textLabel.text = [NSString stringWithFormat:
                                 @"Section %ld, Cell %ld",
                                 (long)indexPath.section,
                                 (long)indexPath.row];

    }

    return cell;

}

Now if we run our app in iPhone Simulator, we will see the results of our work (Figure 4-2).

When a table view is reloaded or refreshed, it queries its data source through the UITableViewDataSource protocol, asking for various bits of information. Among the important methods previously mentioned, the table view will first ask for the number of sections. Each section is responsible for holding rows or cells. After the data source specifies the number of sections, the table view will ask for the number of rows that have to be loaded into each section. The data source gets the zero-based index of each section and, based on this, can decide how many cells have to be loaded into each section.

The table view, after determining the number of cells in the sections, will continue to ask the data source about the view that will represent each cell in each section. You can allocate instances of the UITableViewCell class and return them to the table view. There are, of course, properties that can be set for each cell, including the title, subtitle, and color of each cell, among other properties.

A plain table view with three sections

Figure 4-2. A plain table view with three sections

Get iOS 7 Programming Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.