4.9. Constructing Headers and Footers in Table Views

Problem

You want to create a header and/or a footer for a table view.

Solution

Create a view (could be a label, image view, etc., anything that directly or indirectly subclasses UIView), and assign that view to the header and/or the footer of a section of a table view. You can also allocate specific number of points in height for a header or a footer, as we will soon see.

Discussion

A table view can have multiple headers and footers. Each section in a table view can have its own header and footer, so if you have three sections in a table view, you can have a maximum of three headers and maximum of three footers. You are not obliged to provide headers and footers for any of these sections. It is up to you to tell the table view whether you want a header and/or a footer for a section and you pass these views to the table view through its delegate, should you wish to provide header(s)/footer(s) for section(s) of your table view. Headers and footers in a table view become a part of the table view, meaning that when the table view’s contents scroll, so do the header(s) and footer(s) inside that table view. Let’s have a look at a sample header and footer in a table view (Figure 4-7).

A footer for the top section and the Shortcuts header for the last section of a table view

Figure 4-7. A footer for the top section and the Shortcuts header for the last section of a table view

As you can see, the top section (with items such as “Check Spelling” and “Enable Caps Lock”) has a footer that says “Double tapping the space bar will insert a period followed by a space.” That is the footer of the top section of that table view. The reason why it is a footer rather than a header is because it is attached to the bottom of that section rather than the top. The last section in this table view also has a header that reads “Shortcuts.” The reason why this is a header rather than a footer is because it appears on the top of the section rather than the bottom.

Note

Specifying the height of a header and footer in a section inside a table view is done through methods defined in the UITableViewDataSource. Specifying the actual view that has to be displayed for the header/footer of a section in a table view is done through methods defined in the UITableViewDelegate protocol.

Let’s go ahead and create a simple app with one table view in it. Then let’s provide two labels, of type UILabel, one as the header and the other as the footer of the only section in our table view, and populate this one section with only three cells. In the header we will place the text “Section 1 Header,” and in the footer label we will place the text “Section 1 Footer.” Starting with the header file of our root view controller, we will define a table view:

#import <UIKit/UIKit.h>

@interface Constructing_Headers_and_Footers_in_Table_ViewsViewController
           : UIViewController <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView *myTableView;

@end

Now we will create a grouped table view and load three cells into it:

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

  UITableViewCell *result = nil;

  static NSString *CellIdentifier = @"CellIdentifier";

  result = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

  if (result == nil){
    result = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                    reuseIdentifier:CellIdentifier];
  }

  result.textLabel.text = [[NSString alloc] initWithFormat:@"Cell %ld",
                           (long)indexPath.row];

  return result;

}

- (NSInteger) tableView:(UITableView *)tableView
  numberOfRowsInSection:(NSInteger)section{
  return 3;
}

- (void)viewDidLoad{
  [super viewDidLoad];

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

  self.myTableView.dataSource = self;
  self.myTableView.delegate = self;
  self.myTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth |
                                      UIViewAutoresizingFlexibleHeight;

  [self.view addSubview:self.myTableView];

}

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

Here is the exciting part. We can now use two important methods (which are defined in UITableViewDelegate) to provide a label for the header and another label for the footer of the one section that we have loaded into our table view. These methods are:

tableView:viewForHeaderInSection:

This method expects a return value of type UIView. The view returned from this method will be displayed as the header of the section specified by the viewForHeaderInSection parameter.

tableView:viewForFooterInSection:

This method expects a return value of type UIView. The view returned from this method will be displayed as the footer of the section specified by the viewForFooterInSection parameter.

Our task now is to implement these methods and return an instance of UILabel. On the header label we will enter the text “Section 1 Header,” and on the footer label the text “Section 1 Footer,” as we had planned:

- (UIView *)  tableView:(UITableView *)tableView
 viewForHeaderInSection:(NSInteger)section{

  UILabel *result = nil;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){
    result = [[UILabel alloc] initWithFrame:CGRectZero];
    result.text = @"Section 1 Header";
    result.backgroundColor = [UIColor clearColor];
    [result sizeToFit];
  }

  return result;

}

- (UIView *)  tableView:(UITableView *)tableView
 viewForFooterInSection:(NSInteger)section{

  UILabel *result = nil;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){
    result = [[UILabel alloc] initWithFrame:CGRectZero];
    result.text = @"Section 1 Footer";
    result.backgroundColor = [UIColor clearColor];
    [result sizeToFit];
  }

  return result;

}

If you run your app on the iPhone Simulator now, you will certainly see something strange, as shown in Figure 4-8.

The header and footer labels of a table view are not aligned properly

Figure 4-8. The header and footer labels of a table view are not aligned properly

The reason for this misalignment of the labels is because the table view doesn’t really know the height of these views. To specify the height of the header and footer views, we need to use the following two methods which are defined in the UITableViewDelegate protocol:

tableView:heightForHeaderInSection:

The return value of this method is of type CGFloat, and it specifies the height of the header for a section in a table view. The section’s index is passed through the heightForHeaderInSection parameter.

tableView:heightForFooterInSection:

The return value of this method is of type CGFloat, and it specifies the height of the footer for a section in a table view. The section’s index is passed through the heightForHeaderInSection parameter.

- (CGFloat)     tableView:(UITableView *)tableView
 heightForHeaderInSection:(NSInteger)section{

  CGFloat result = 0.0f;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){
    result = 30.0f;
  }

  return result;

}

- (CGFloat)     tableView:(UITableView *)tableView
 heightForFooterInSection:(NSInteger)section{

  CGFloat result = 0.0f;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){
    result = 30.0f;
  }

  return result;

}

Running the app, you can see that the height of the header and the footer labels is fixed. There is still something wrong with the code we’ve written—the left margin of our header and footer labels. Take a look for yourself in Figure 4-9.

The left margin of our header and footer labels is not correct

Figure 4-9. The left margin of our header and footer labels is not correct

The reason for this is that the table view, by default, places header and footer views at x point 0.0f. You might think that changing the frame of your header and footer labels will fix this issue, but unfortunately it doesn’t. The solution to this problem is creating a generic UIView and placing your header and footer labels on that view. Return the generic view as the header/footer, but change the x position of your labels within the generic view. We now need to modify our implementation of the tableView:viewForHeaderInSection: and the tableView:viewForFooterInSection: methods:

- (UIView *)  tableView:(UITableView *)tableView
 viewForHeaderInSection:(NSInteger)section{

  UIView *result = nil;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
    label.text = @"Section 1 Header";
    label.backgroundColor = [UIColor clearColor];
    [label sizeToFit];

    /* Move the label 10 points to the right */
    label.frame = CGRectMake(label.frame.origin.x + 10.0f,
                             5.0f, /* Go 5 points down in y axis */
                             label.frame.size.width,
                             label.frame.size.height);

    /* Give the container view 10 points more in width than our label
     because the label needs a 10 extra points left-margin */    
    CGRect resultFrame = CGRectMake(0.0f,
                                    0.0f,
                                    label.frame.size.width + 10.0f,
                                    label.frame.size.height);
    result = [[UIView alloc] initWithFrame:resultFrame];
    [result addSubview:label];

  }

  return result;

}

- (UIView *)  tableView:(UITableView *)tableView
 viewForFooterInSection:(NSInteger)section{

  UIView *result = nil;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
    label.text = @"Section 1 Footer";
    label.backgroundColor = [UIColor clearColor];
    [label sizeToFit];

    /* Move the label 10 points to the right */
    label.frame = CGRectMake(label.frame.origin.x + 10.0f,
                             5.0f, /* Go 5 points down in y axis */
                             label.frame.size.width,
                             label.frame.size.height);

    /* Give the container view 10 points more in width than our label
     because the label needs a 10 extra points left-margin */
    CGRect resultFrame = CGRectMake(0.0f,
                                    0.0f,
                                    label.frame.size.width + 10.0f,
                                    label.frame.size.height);
    result = [[UIView alloc] initWithFrame:resultFrame];
    [result addSubview:label];

  }

  return result;

}

Now if you run your app, you will get results similar to Figure 4-10.

Our header and footer labels displayed in a table view

Figure 4-10. Our header and footer labels displayed in a table view

With the methods you just learned, you can even place images as the header/footer of your table views. Instances of UIImageView have UIView as their superclass, so you can easily place your images in image views and return them as headers/footers of a table view. If all you want to place is text as the header/footer of table views, you can use two handy methods defined in the UITableViewDataSource protocol, which will save you a lot of hassle. Instead of creating your own labels and returning them as headers/footers of your table view, you can simply use these methods:

tableView:titleForHeaderInSection:

The return value of this method is of type NSString. This string will automatically be placed inside a label by the table view and will be displayed as the header of the section, which is specified in the titleForHeaderInSection parameter.

tableView:titleForFooterInSection:

The return value of this method is of type NSString. This string will automatically be placed inside a label by the table view and will be displayed as the footer of the section, which is specified in the titleForFooterInSection parameter.

So to make our app’s code simpler, let’s get rid of our implementation of the tableView:viewForHeaderInSection: and the tableView:viewForFooterInSection: methods, and replace them with the implementation of the tableView:titleForHeaderInSection: and the tableView:titleForFooterInSection: methods:

- (NSString *) tableView:(UITableView *)tableView
 titleForHeaderInSection:(NSInteger)section{

  NSString *result = nil;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){
    result = @"Section 1 Header";
  }

  return result;

}

- (NSString *) tableView:(UITableView *)tableView
 titleForFooterInSection:(NSInteger)section{

  NSString *result = nil;

  if ([tableView isEqual:self.myTableView] &&
      section == 0){
    result = @"Section 1 Footer";
  }

  return result;

}

Now run your app in the iPhone Simulator, and you will see that the table view has automatically created a left-aligned label for the header and a center-aligned label for the footer of the only section in our table view. The alignment of these labels is the default alignment that every table view creates its header/footer labels with (see Figure 4-11).

A table view rendering text in headers and footers

Figure 4-11. A table view rendering text in headers and footers

Get iOS 6 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.