51

Hi I want to use UITableHeaderFooterView in my app and i am doing this:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    [_tableView registerClass:[M3CTableViewCell class] forCellReuseIdentifier:@"cell"];
    [_tableView registerClass:[M3CHeaderFooter class] forHeaderFooterViewReuseIdentifier:@"footer"];

}

- (UITableViewHeaderFooterView *)footerViewForSection:(NSInteger)section {
    M3CHeaderFooter * footer = [[M3CHeaderFooter alloc]initWithReuseIdentifier:@"footer"];
    footer.textLabel.text = @"Test";
    return footer;
}

By doing this I am not getting anything at Footer's place. And this method is not even getting called but I think this method is part of UITableViewDelegate protocol.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Ashutosh
  • 5,614
  • 13
  • 52
  • 84

9 Answers9

70

Using the new iOS 6 feature of reusable header/footer views involves two steps. You seem to be doing only the first step.

First step: you're telling the table view what class to use for the section header view, by registering your custom subclass of UITableViewHeaderFooterView (I assume your M3CHeaderFooter is a subclass of UITableViewHeaderFooterView).

Second step: Tell the table view what view to use (AND reuse) for a header section by implementing the tableView delegate method

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

So in your viewDidLoad you'd implement something like this:

    // ****** Do Step One ******
    [_tableView registerClass:[M3CHeaderFooter class] forHeaderFooterViewReuseIdentifier:@"TableViewSectionHeaderViewIdentifier"];

Then you'd implement the table View delegate method in the class where you're creating and displaying your table view:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 40.0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString *headerReuseIdentifier = @"TableViewSectionHeaderViewIdentifier";

    // ****** Do Step Two *********
    M3CHeaderFooter *sectionHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
   // Display specific header title
   sectionHeaderView.textLabel.text = @"specific title";   

    return sectionHeaderView;    
}

Now mind you that you do not need to subclass UITableViewHeaderFooterView in order to use it. Before iOS 6, if you wanted to have a header view for a section, you'd implement the above tableView delegate method and tell the table view what view to use for each section. So each section had a different instance of a UIView which you provided. This means that if your tableView had 100 sections, and inside the delegate method you created a new instance of a UIView, then you would have given the tableView 100 UIViews for the 100 section headers that were displayed.

Using the new feature of reusable header/footer views, you create an instance of a UITableViewHeaderFooterView and the system reuses it for each displayed section header.

If you wanted to have a reusable UITableViewHeaderFooterView without subclassing then you simply change your viewDidLoad to this:

// Register the class for a header view reuse.
[_buttomTableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:@"TableViewSectionHeaderViewIdentifier"];

and then your delegate method to this:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 40.0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString *headerReuseIdentifier = @"TableViewSectionHeaderViewIdentifier";

   // Reuse the instance that was created in viewDidLoad, or make a new one if not enough.
    UITableViewHeaderFooterView *sectionHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
    sectionHeaderView.textLabel.text = @"Non subclassed header";

    return sectionHeaderView;

}

I hope that was clear enough.

EDIT: When subclassing the header view, you can implement code similar to the following if you wish to add a custom view to the headerView:

        // Add any optional custom views of your own
    UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 30.0)];
    [customView setBackgroundColor:[UIColor blueColor]];

    [sectionHeaderView.contentView addSubview:customView];

Doing this in the subclass, as opposed to viewForHeaderInSection: delegate method (as noted below by Matthias), will ensure that only one instance of any subviews are created. You can then add any properties within the subclass that will allow you to access your custom subview.

Raz
  • 1,387
  • 12
  • 13
  • in your code `[aTableViewHeaderFooterView class]` is just `[UITableViewHeaderFooterView class]`, and there's no point in creating an instance – user102008 Apr 30 '13 at 00:39
  • Yes, I'm talking about the second one. For the first one, it's exactly the same as `[M3CHeaderFooter class]`. Creating an instance is completely useless, and whatever things you did to customize that instance is also useless. – user102008 May 01 '13 at 02:43
  • @user102008, I see what you're referring to now, and yes you are correct. I edited my code to reflect that. Thanks. – Raz May 01 '13 at 02:51
  • am I missing something or is the second method a really bad idea because you add a new view to the headerView each time the `viewForHeaderInSection:` method is called? – Matthias Bauch Jul 10 '13 at 21:38
  • @MatthiasBauch You are correct. The code "as is" will cause a leak. I included that bit of code to illustrate how to go about if wishing to add a custom view to the contentView of the header view, however I can see that it can be a source of bug if the above code is used "as is". I will separate that bit and give a comments. Thanks for feedback. – Raz Jul 11 '13 at 14:59
  • In my testing I have found that any sub-views added to the UITableViewHeaderFooterView seem to be 'removed' in that any subsequent dequeue will not include them. This leads to the question of exactly what is being re-used? As far I can tell there is no re-use at all... – Christopher King Mar 13 '14 at 01:07
  • I have XIB with two views. Based on some condition, one of the view has to be displayed as header. How to achieve it using above steps? – Satyam Feb 20 '15 at 10:16
9

UITableViewHeaderFooterView is one of the few places I would programmatically handle the view rather than use Storyboard or a XIB. Since you cannot officially use appearance proxy and there is no IB way to do it without abusing UITableViewCells. I do it the old-fashioned way and just use the tag on the label to fetch the custom elements.

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kSectionHeaderReuseIdentifier];
    if (headerView == nil) {
        [tableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:kSectionHeaderReuseIdentifier];
        headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kSectionHeaderReuseIdentifier];
    }

    UILabel *titleLabel = (UILabel *)[headerView.contentView viewWithTag:1];
    if (titleLabel == nil) {
        UIColor *backgroundColor = [UIColor blackColor];
        headerView.contentView.backgroundColor = backgroundColor;
        titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 0.0f, 300.0f, 44.0f)];
        titleLabel.textColor = [UIColor whiteColor];
        titleLabel.backgroundColor = backgroundColor;
        titleLabel.shadowOffset = CGSizeMake(0.0f, 0.0f);
        titleLabel.tag = 1;
        titleLabel.font = [UIFont systemFontOfSize:24.0f];
        [headerView.contentView addSubview:titleLabel];
    }

    NSString *sectionTitle = [self.sections objectAtIndex:section];
    if (sectionTitle == nil) {
        sectionTitle = @"Missing Title";
    }

    titleLabel.text = sectionTitle;

    return headerView;
}
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
  • In my testing of this technique I have found that the UILabel returned from the viewWithTag call will always be nil, so this would not seem to be achieving the re-use intended. Based on this I would say that if you want your header-view to contain subviews that are themselves not re-created every time then you need to sub-class UITableViewHeaderFooterView. – Christopher King Mar 13 '14 at 01:03
  • @ChristopherKing I found that the issue was a bad copy/paste from my production code. You'll notice that I needed to register the backing class. – Cameron Lowell Palmer Jun 15 '15 at 07:51
  • @CameronLowellPalmer the `reloadSections` method of UItableview does not update header and footer views right? Have a look at this: https://stackoverflow.com/questions/46531768/uitableview-grouped-section-footer-not-updating-after-reload – nr5 Oct 03 '17 at 02:39
7

This is an old post and has good answers, but I wanted to share another work-around for a very similar issue I experienced.

At first, I used:

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

With a custom prototype cell for my header view. Subclassing UITableViewCell as such

    static NSString *cellIdentifier = @"CustomHeaderCell";
CustomHeaderCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

However, when animating TableView cells above section headers (making them twice as tall) the header view would disappear. This, as pointed out, is because the implementation only supplied a view, not a re-usable view.

Instead of forgoing everything with the customized prototype cell, I implemented the UITableViewHeaderFooterWithIdentifier and set it as the prototyped cell's contentView, without subclassing UITableViewHeaderFooterWithIdentifier.

  static NSString *customHeaderViewIdentifier = @"CustomHeaderView";
UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:customHeaderViewIdentifier];

headerView = (UITableViewHeaderFooterView *)cell.contentView;

I realize this creates two instances of the header view (at least I think it would..) however it does allow you to keep the benefits of a customized prototype cell without doing everything programatically.

Full code:

  // viewDidLoad
    [myTableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:@"CustomHeaderView"];

// Implement your custom header
 -(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
     static NSString *cellIdentifier = @"CustomHeaderCell";
    CustomHeaderCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    static NSString *customHeaderViewIdentifier = @"CustomHeaderView";
    UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:customHeaderViewIdentifier];

// do your cell-specific code here
// eg. cell.myCustomLabel.text = @"my custom text"

    headerView = (UITableViewHeaderFooterView *)cell.contentView;

return headerView;
}
EricK
  • 71
  • 1
  • 4
  • convenient to use a CellPrototype as the header view. – Vassily May 05 '15 at 11:51
  • 1
    I have a question: what's the point of dequeuing a header if you assign a content view to it just after? – Lucas May 19 '15 at 14:50
  • 1
    I agree with @Lucas, you can simply `return cell.contentView` in the third line of `viewForHeaderInSection`, inserting some customization code before returning. Careful as casting `cell.contentView` may break in future iOS versions as there is no guarantee that a cell's content view is a `UITableViewHeaderFooterView` – Christophe Fondacci Aug 27 '15 at 21:48
5

There are a few ways of approaching this, but here is one a solution in Swift: the idea here is that we have a subclass of UITableViewHeaderFooterView called SNStockPickerTableHeaderView; it exposes a method called, configureTextLabel() that when called, sets the font and the color of the text label. We call this method only after the title has been set, that is from, willDisplayHeaderView, and the font gets correctly set.

The header view also supports a custom line separator to set it apart from the rest of the cells.

// MARK: UITableViewDelegate

func tableView(tableView:UITableView, willDisplayHeaderView view:UIView, forSection section:Int) {
  if let headerView:SNStockPickerTableHeaderView = view as? SNStockPickerTableHeaderView {
    headerView.configureTextLabel()
  }
}

func tableView(tableView:UITableView, viewForHeaderInSection section:Int) -> UIView? {
  var headerView:SNStockPickerTableHeaderView? = tableView.dequeueReusableHeaderFooterViewWithIdentifier(kSNStockPickerTableHeaderViewReuseIdentifier) as? SNStockPickerTableHeaderView
  if (headerView == nil) {
    // Here we get to customize the section, pass in background color, text 
    // color, line separator color, etc. 
    headerView = SNStockPickerTableHeaderView(backgroundColor:backgroundColor,
      textColor:primaryTextColor,
      lineSeparatorColor:primaryTextColor)
  }
  return headerView!
}

And here is the custom UITableViewHeaderFooterView:

import Foundation
import UIKit

private let kSNStockPickerTableHeaderViewLineSeparatorHeight:CGFloat = 0.5
private let kSNStockPickerTableHeaderViewTitleFont = UIFont(name:"HelveticaNeue-Light", size:12)

let kSNStockPickerTableHeaderViewReuseIdentifier:String = "stock_picker_table_view_header_reuse_identifier"

class SNStockPickerTableHeaderView: UITableViewHeaderFooterView {

  private var lineSeparatorView:UIView?
  private var textColor:UIColor?

  required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  // We must implement this, since the designated init of the parent class
  // calls this by default!
  override init(frame:CGRect) {
    super.init(frame:frame)
  }

  init(backgroundColor:UIColor, textColor:UIColor, lineSeparatorColor:UIColor) {
    super.init(reuseIdentifier:kSNStockPickerTableHeaderViewReuseIdentifier)
    contentView.backgroundColor = backgroundColor
    self.textColor = textColor
    addLineSeparator(textColor)
  }

  // MARK: Layout

  override func layoutSubviews() {
    super.layoutSubviews()
    let lineSeparatorViewY = CGRectGetHeight(self.bounds) - kSNStockPickerTableHeaderViewLineSeparatorHeight
    lineSeparatorView!.frame = CGRectMake(0,
      lineSeparatorViewY,
      CGRectGetWidth(self.bounds),
      kSNStockPickerTableHeaderViewLineSeparatorHeight)
  }

  // MARK: Public API

  func configureTextLabel() {
    textLabel.textColor = textColor
    textLabel.font = kSNStockPickerTableHeaderViewTitleFont
  }

  // MARK: Private

  func addLineSeparator(lineSeparatorColor:UIColor) {
    lineSeparatorView = UIView(frame:CGRectZero)
    lineSeparatorView!.backgroundColor = lineSeparatorColor
    contentView.addSubview(lineSeparatorView!)
  }
}

Here is the result, see section header for, "Popular Stocks":

                              enter image description here

Zorayr
  • 23,770
  • 8
  • 136
  • 129
3

I can't comment under Cameron Lowell Palmer post but to answer Christopher King, there is a simple way to ensure the re-use without sub-classing UITableViewHeaderFooterView and yet still using custom subviews.

First, do NOT register the class for a header view reuse.

Then in tableView:viewForHeaderInSection: you simply have to create UITableViewHeaderFooterView when needed:

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

    UILabel *titleLabel = nil;

    UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kYourTableViewReusableHeaderIdentifier];

    if (headerView == nil) {

        headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:kYourTableViewReusableHeaderIdentifier];

        titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(...)];
        titleLabel.tag = 1;
        // ... setup titleLabel 

        [headerView.contentView addSubview:titleLabel];
    } else {
        // headerView is REUSED
        titleLabel = (UILabel *)[headerView.contentView viewWithTag:1];
    }

    NSString *sectionTitle = (...); // Fetch value for current section
    if (sectionTitle == nil) {
        sectionTitle = @"Missing Title";
    }

    titleLabel.text = sectionTitle;

    return headerView;
}
Community
  • 1
  • 1
Bluezen
  • 850
  • 9
  • 13
  • Just wondering... what is the advantage of *not* subclassing all this? You still don't have to register the class if you prefer manual creation, although with a subclass it's even easier as creation is handled for you. Plus, if you do, you don't need to find views by ID, and it also better separates the setup/definition (the view itself) from its usage (where it's asked for) meaning you can also now unit-test your view. Even a fileprivate class would be better IMHO. Again, this of course works fine. I just don't understand the avoiding subclassing. It just seems messier/requires more work. – Mark A. Donohoe Feb 19 '19 at 16:41
1

Here is a "quick-and-dirty" way to get this going. It will make a small blue label in the header. I've confirmed that this renders OK in iOS 6 and iOS 7.

in your UITableViewDelegate:

 -(void)viewDidLoad
{
...
    [self.table registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:@"Header"];
...
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 34.;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UITableViewHeaderFooterView *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"Header"];

    UILabel *leftlabel = [[UILabel alloc] initWithFrame:CGRectMake(0., 0., 400., 34.)];
    [leftlabel setBackgroundColor:[UIColor blueColor]];

    [header.contentView addSubview:leftlabel];
    return header;
}
maz
  • 8,056
  • 4
  • 26
  • 25
0

In case it gets lost in the thorough answers above, the thing that people are likely missing (compared to the standard cellForRowAtIndexPath: method) is that you must register the class used for the section header.

[tableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:@"SectionHeader"];

Try adding registerClass:forHeaderFooterViewReuseIdentifier: and see if it starts working.

pkamb
  • 33,281
  • 23
  • 160
  • 191
0

One of the reasons that method may not be being called is the style of the table. Standard vs Grouped handles headers/footers differently. That may explain why it's not getting called.

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
-1
  1. Set delegate property of UITableView instance to reference to the controller that implements next methods:

  2. Method that returns view of section footer:

    Asks the delegate for a view object to display in the footer of the specified section of the table view. - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section

  3. Height of view in section footer:

    Asks the delegate for the height to use for the footer of a particular section.

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

Nekto
  • 17,837
  • 1
  • 55
  • 65
  • I know how to do that i am just trying to figure out how to use this new UITableViewHeaderFooterView properly. – Ashutosh Oct 15 '12 at 17:37