How to Implement Custom Table View Section Headers and Footers with Storyboard

UitableviewIos5Uistoryboard

Uitableview Problem Overview


Without using a storyboard we could simply drag a UIView onto the canvas, lay it out and then set it in the tableView:viewForHeaderInSection or tableView:viewForFooterInSection delegate methods.

How do we accomplish this with a StoryBoard where we cannot drag a UIView onto the canvas

Uitableview Solutions


Solution 1 - Uitableview

Just use a prototype cell as your section header and / or footer.

  • add an extra cell and put your desired elements in it.
  • set the identifier to something specific (in my case SectionHeader)
  • implement the tableView:viewForHeaderInSection: method or the tableView:viewForFooterInSection: method
  • use [tableView dequeueReusableCellWithIdentifier:] to get the header
  • implement the tableView:heightForHeaderInSection: method.

(see screenhot)

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    static NSString *CellIdentifier = @"SectionHeader"; 
    UITableViewCell *headerView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (headerView == nil){
        [NSException raise:@"headerView == nil.." format:@"No cells with matching CellIdentifier loaded from your storyboard"];
    }
    return headerView;
}  

Edit: How to change the header title (commented question):

  1. Add a label to the header cell
  2. set the tag of the label to a specific number (e.g. 123)
  3. In your tableView:viewForHeaderInSection: method get the label by calling:

    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 

4. Now you can use the label to set a new title:

    [label setText:@"New Title"];

Solution 2 - Uitableview

I know this question was for iOS 5, but for the benefit of future readers, note that effective iOS 6 we can now use dequeueReusableHeaderFooterViewWithIdentifier instead of dequeueReusableCellWithIdentifier.

So in viewDidLoad, call either registerNib:forHeaderFooterViewReuseIdentifier: or registerClass:forHeaderFooterViewReuseIdentifier:. Then in viewForHeaderInSection, call tableView:dequeueReusableHeaderFooterViewWithIdentifier:. You do not use a cell prototype with this API (it's either a NIB-based view or a programmatically created view), but this is the new API for dequeued headers and footers.

Solution 3 - Uitableview

In iOS 6.0 and above, things have changed with the new dequeueReusableHeaderFooterViewWithIdentifier API.

I have written a guide (tested on iOS 9), which can be summarised as such:

  1. Subclass UITableViewHeaderFooterView
  2. Create Nib with the subclass view, and add 1 container view which contains all other views in the header/footer
  3. Register the Nib in viewDidLoad
  4. Implement viewForHeaderInSection and use dequeueReusableHeaderFooterViewWithIdentifier to get back the header/footer

Solution 4 - Uitableview

I got it working in iOS7 using a prototype cell in the storyboard. I have a button in my custom section header view that triggers a segue that is set up in the storyboard.

Start with Tieme's solution

As pedro.m points out, the problem with this is that tapping the section header causes the first cell in the section to be selected.

As Paul Von points out, this is fixed by returning the cell's contentView instead of the whole cell.

However, as Hons points out, a long press on said section header will crash the app.

The solution is to remove any gestureRecognizers from contentView.

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
     static NSString *CellIdentifier = @"SectionHeader";
     UITableViewCell *sectionHeaderView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     
     while (sectionHeaderView.contentView.gestureRecognizers.count) {
         [sectionHeaderView.contentView removeGestureRecognizer:[sectionHeaderView.contentView.gestureRecognizers objectAtIndex:0]];
     }

     return sectionHeaderView.contentView; }

If you aren't using gestures in your section header views, this little hack seems to get it done.

Solution 5 - Uitableview

If you use storyboards you can use a prototype cell in the tableview to layout your header view. Set an unique id and viewForHeaderInSection you can dequeue the cell with that ID and cast it to a UIView.

Solution 6 - Uitableview

If you need a Swift Implementation of this follow the directions on the accepted answer and then in you UITableViewController implement the following methods:

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    return tableView.dequeueReusableCell(withIdentifier: "CustomHeader")
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 75
}

Solution 7 - Uitableview

The solution I came up with is basically the same solution used before the introduction of storyboards.

Create a new, empty interface class file. Drag a UIView on to the canvas, layout as desired.

Load the nib manually, assign to the appropriate header/footer section in viewForHeaderInSection or viewForFooterInSection delegate methods.

I had hope that Apple simplified this scenario with storyboards and kept looking for a better or simpler solution. For example custom table headers and footers are straight forward to add.

Solution 8 - Uitableview

When you return cell's contentView you will have a 2 problems:

  1. crash related to gestures
  2. you don't reusing contentView (every time on viewForHeaderInSection call, you creating new cell)

Solution:

Wrapper class for table header\footer. It is just container, inherited from UITableViewHeaderFooterView, which holds cell inside

https://github.com/Magnat12/MGTableViewHeaderWrapperView.git

Register class in your UITableView (for example, in viewDidLoad)

- (void)viewDidLoad {
	[super viewDidLoad];
    [self.tableView registerClass:[MGTableViewHeaderWrapperView class] forHeaderFooterViewReuseIdentifier:@"ProfileEditSectionHeader"];
}

In your UITableViewDelegate:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
	MGTableViewHeaderWrapperView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"ProfileEditSectionHeader"];

    // init your custom cell
	ProfileEditSectionTitleTableCell *cell = (ProfileEditSectionTitleTableCell * ) view.cell;
	if (!cell) {
		cell = [tableView dequeueReusableCellWithIdentifier:@"ProfileEditSectionTitleTableCell"];
		view.cell = cell;
	}

	// Do something with your cell

	return view;
}

Solution 9 - Uitableview

I've been in trouble within a scenario where Header was never reused even doing all the proper steps.

So as a tip note to everyone who want to achieve the situation of show empty sections (0 rows) be warn that:

dequeueReusableHeaderFooterViewWithIdentifier will not reuse the header until you return at least one row

Hope it helps

Solution 10 - Uitableview

I used to do the following to create header/footer views lazily:

  • Add a freeform view controller for the section header/footer to the storyboard
  • Handle all stuff for the header in the view controller
  • In the table view controller provide a mutable array of view controllers for the section headers/footers repopulated with [NSNull null]
  • In viewForHeaderInSection/viewForFooterInSection if view controller does not yet exist, create it with storyboards instantiateViewControllerWithIdentifier, remember it in the array and return the view controllers view

Solution 11 - Uitableview

Similar to laszlo answer but you can reuse the same prototype cell for both the table cells and the section header cell. Add the first two functions below to your UIViewController subClass

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell") as! DataCell
    cell.data1Label.text = "DATA KEY"
    cell.data2Label.text = "DATA VALUE"
    return cell
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 75
}

// Example of regular data cell dataDelegate to round out the example
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as! PlayerCell
    
    cell.data1Label.text = "\(dataList[indexPath.row].key)"
    cell.data2Label.text = "\(dataList[indexPath.row].value)"
    return cell
}

Solution 12 - Uitableview

To follow up on Damon's suggestion, here is how I made the header selectable just like a normal row with a disclosure indicator.

I added a Button subclassed from UIButton (subclass name "ButtonWithArgument") to the header's prototype cell and deleted the title text (the bold "Title" text is another UILabel in the prototype cell)

Button In Interface Builder

then set the Button to the entire header view, and added a disclosure indicator with Avario's trick

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
	static NSString *CellIdentifier = @"PersonGroupHeader";
	UITableViewCell *headerView = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	if(headerView == nil)
	{
		[NSException raise:@"headerView == nil, PersonGroupTableViewController" format:[NSString stringWithFormat:@"Storyboard does not have prototype cell with identifier %@",CellIdentifier]];
	}
	
	//  https://stackoverflow.com/a/24044628/3075839
	while(headerView.contentView.gestureRecognizers.count)
	{
		[headerView.contentView removeGestureRecognizer:[headerView.contentView.gestureRecognizers objectAtIndex:0]];
	}
	

	ButtonWithArgument *button = (ButtonWithArgument *)[headerView viewWithTag:4];
	button.frame = headerView.bounds; // set tap area to entire header view
	button.argument = [[NSNumber alloc] initWithInteger:section]; // from ButtonWithArguments subclass
	[button addTarget:self action:@selector(headerViewTap:) forControlEvents:UIControlEventTouchUpInside];

	// https://stackoverflow.com/a/20821178/3075839
	UITableViewCell *disclosure = [[UITableViewCell alloc] init];
	disclosure.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
	disclosure.userInteractionEnabled = NO;
	disclosure.frame = CGRectMake(button.bounds.origin.x + button.bounds.size.width - 20 - 5, // disclosure 20 px wide, right margin 5 px
          (button.bounds.size.height - 20) / 2,
          20,
          20);
	[button addSubview:disclosure];

	// configure header title text
	
	return headerView.contentView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
	return 35.0f;
}

-(void) headerViewTap:(UIGestureRecognizer *)gestureRecognizer;
{
	NSLog(@"header tap");
	NSInteger section = ((NSNumber *)sender.argument).integerValue;
	// do something here
}

ButtonWithArgument.h

#import <UIKit/UIKit.h>

@interface ButtonWithArgument : UIButton
@property (nonatomic, strong) NSObject *argument;
@end

ButtonWithArgument.m

#import "ButtonWithArgument.h"
@implementation ButtonWithArgument
@end

Solution 13 - Uitableview

You should use Tieme's solution as a base but forget about the viewWithTag: and other fishy approaches, instead try to reload your header (by reloading that section).

So after you sat up your custom cell-header view with all the fancy AutoLayout stuff, just dequeue it and return the contentView after your set up, like:

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
 static NSString *CellIdentifier = @"SectionHeader"; 

    SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    sectionHeaderCell.myPrettyLabel.text = @"Greetings";
    sectionHeaderCell.contentView.backgroundColor = [UIColor whiteColor]; // don't leave this transparent

    return sectionHeaderCell.contentView;
}  

Solution 14 - Uitableview

What about a solution where the header is based on a view array :

class myViewController: UIViewController {
	var header: [UILabel] = myStringArray.map { (thisTitle: String) -> UILabel in
		let headerView = UILabel()
			headerView.text = thisTitle
	return(headerView)
}

Next in the delegate :

extension myViewController: UITableViewDelegate {
	func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
		return(header[section])
	}
}

Solution 15 - Uitableview

  1. Add cell in StoryBoard, and set reuseidentified

    sb

  2. Code

     class TP_TaskViewTableViewSectionHeader: UITableViewCell{
     }
    

    and

    link

  3. Use:

     func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
         let header = tableView.dequeueReusableCell(withIdentifier: "header", for: IndexPath.init(row: 0, section: section))
         return header
     }
    

Solution 16 - Uitableview

Here is @Vitaliy Gozhenko's answer, in Swift.
To summarize you will create a UITableViewHeaderFooterView that contains a UITableViewCell. This UITableViewCell will be "dequeuable" and you can design it in your storyboard.

  1. Create a UITableViewHeaderFooterView class

     class CustomHeaderFooterView: UITableViewHeaderFooterView {
     var cell : UITableViewCell? {
         willSet {
             cell?.removeFromSuperview()
         }
         didSet {
             if let cell = cell {
                 cell.frame = self.bounds
                 cell.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth]
                 self.contentView.backgroundColor = UIColor .clearColor()
                 self.contentView .addSubview(cell)
             }
         }
     }
    
  2. Plug your tableview with this class in your viewDidLoad function:

     self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. When asking, for a section header, dequeue a CustomHeaderFooterView, and insert a cell into it

     func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
         let view = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("SECTION_ID") as! CustomHeaderFooterView
         if view.cell == nil {
             let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell")
             view.cell = cell;
         }
         
         // Fill the cell with data here
         
         return view;
     }
    

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSeamusView Question on Stackoverflow
Solution 1 - UitableviewTiemeView Answer on Stackoverflow
Solution 2 - UitableviewRobView Answer on Stackoverflow
Solution 3 - UitableviewsamwizeView Answer on Stackoverflow
Solution 4 - UitableviewdamonView Answer on Stackoverflow
Solution 5 - UitableviewbarkstenView Answer on Stackoverflow
Solution 6 - UitableviewJZ.View Answer on Stackoverflow
Solution 7 - UitableviewSeamusView Answer on Stackoverflow
Solution 8 - UitableviewVitalii GozhenkoView Answer on Stackoverflow
Solution 9 - UitableviewJuan Pedro LozanoView Answer on Stackoverflow
Solution 10 - UitableviewVipera BerusView Answer on Stackoverflow
Solution 11 - UitableviewRichard LegaultView Answer on Stackoverflow
Solution 12 - UitableviewMarkFView Answer on Stackoverflow
Solution 13 - UitableviewLaszloView Answer on Stackoverflow
Solution 14 - UitableviewCyrIngView Answer on Stackoverflow
Solution 15 - UitableviewleonardosccdView Answer on Stackoverflow
Solution 16 - UitableviewCedricSoubrieView Answer on Stackoverflow