180

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

Kumar KL
  • 15,315
  • 9
  • 38
  • 60
Seamus
  • 3,191
  • 3
  • 22
  • 20

16 Answers16

386

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]; 
  1. Now you can use the label to set a new title:
    [label setText:@"New Title"];
SahilS
  • 396
  • 5
  • 7
Tieme
  • 62,602
  • 20
  • 102
  • 156
  • 2
    doing this way, how to change header title? – Paulius Vindzigelskis Jul 19 '12 at 07:48
  • 2
    I did this, but I have one issue that might be related. When I tap on the section header, it selects the table cell below it. This is undesired, as I'd like nothing to happen. I tried setting the userInteractionEnabled to false but does not work. – Pedro Mancheno Jul 30 '12 at 17:24
  • this does not occur with my project. Sounds like strange behavoir. – Tieme Aug 27 '12 at 20:00
  • 1
    I fixed it by adding a UIView to the UITableViewCell which I named contentView. I then created an outlet for it and after loading the UITableViewCell, instead of casting it and returning it in the delegate method, I return cell.contentView. – Pedro Mancheno Oct 19 '12 at 14:12
  • This solution doesn't quite work for static cells. My table view has one section with static cells (all different) and 3 other sections with prototype cells (all similar within each section). At best, the proposed solution will require significant tweaking – Jean-Denis Muys Mar 29 '13 at 01:49
  • What kind of tweaking? What is going wrong in the proposed solution? – Tieme Mar 29 '13 at 22:24
  • I got this error: `AX ERROR: Could not find my mock parent, most likely I am stale.` why? – akin May 22 '13 at 13:39
  • Did you google the error? Check out this post. http://stackoverflow.com/questions/6878530/ax-error-when-using-accessibility-inspector-for-ios-app – Tieme May 22 '13 at 14:45
  • Headers don't show up for me until I implemented `- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section` – adbie Sep 17 '13 at 03:58
  • Yeah, thats correct. Updated the answer. "In iOS 5.0 and later, you must return the actual height for each section header in `tableView:heightForHeaderInSection:`" -- http://goo.gl/weL7mO – Tieme Sep 24 '13 at 08:12
  • When I reload the section with the header the header disappears and I get "no index path for table cell being reused" – shim Oct 08 '13 at 02:59
  • That's an iOS 7 issue, see http://stackoverflow.com/questions/12772197/what-is-the-meaning-of-the-no-index-path-for-table-cell-being-reused-message-i – Tieme Oct 08 '13 at 09:05
  • 2
    Current iOS 7 has multiple subtle issues if you use a `UITableViewCell` as your header. Notably, the delegate methods dealing with header views (e.g. `-tableView:willDisplayHeaderView:forSection:`) never fire, and you can't retrieve the header view using `-[UITableView headerViewForSection:]`. – Lily Ballard May 01 '14 at 22:06
  • 7
    Late to the party, but to disable clicks on the section header view, simply return cell.contentView in the `viewForHeaderInSection ` method (no need to add any custom UIViews). – paulvs May 29 '14 at 14:46
  • 3
    @PaulVon I tried to do that in my latest project, but if you do that just try to longpress on one of your headers and it will crash – Hons May 30 '14 at 09:44
  • 1
    @Hons good catch! [Here's a workaround] (http://stackoverflow.com/a/24044628/283165) – damon Jun 04 '14 at 18:13
  • Why you use tag instead of outlets? You could use a subclass of UITableViewCell with a label outlet property. – andreacipriani Sep 16 '14 at 10:57
  • This is a simple way to accomplish what you want without having to create a subclass. – Tieme Sep 16 '14 at 11:19
  • I had more than one section headers, and I ran into an issue where tapping the header was tapping the first row in the section. I had to turn off "User Interaction Enabled" on the header prototype cell to fix that. – Myxtic Oct 31 '14 at 12:52
  • I ran into problems with swipe to delete, every header and footer moved along. Needed to add the contentView and set the frames by hand. Not so elegant. – Lucas van Dongen Feb 11 '15 at 19:33
  • 13
    In iOS 6.0, `dequeueReusableHeaderFooterViewWithIdentifier` is introduced, and is now preferred over this answer. But using that correctly now takes more steps. I have guide @ http://samwize.com/2015/11/06/guide-to-customizing-uitableview-section-header-footer/. – samwize Nov 07 '15 at 11:07
  • 3
    Do not create your sectionHeaderViews using UITableViewCells, it will create unexpected behaviour. Use a UIView instead. – AppreciateIt Jun 01 '16 at 08:19
  • @AppreciateIt How can I add separator lines for sections by using a UIView? – iamhx Apr 22 '17 at 04:42
  • @iamhx use a UIView that is shaped like a line, sounds hacky but I don't think section headers are mean't to have separator lines – AppreciateIt Apr 22 '17 at 16:03
  • 4
    Please don't ever use `UITableViewCell` as a header view. You'll get very difficult to debug visual glitches - header will sometimes disappear because of how cells are dequeued and you'll be looking for hours why is that until you realize `UITableViewCell` does not belong in `UITableView` header. – raven_raven Sep 28 '17 at 11:23
99

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 13
    *sigh* A shame that these two clean answers about using a NIB instead of a proto cell are currently under 5 votes, and the "hack it with proto cells" answer is up above 200. – Benjohn Jul 15 '15 at 10:07
  • 5
    The difference is that with the hack from Tieme you can make your design in the same storyboard and not use a separate Xib – CedricSoubrie May 04 '16 at 14:15
  • Can you somehow avoid using separate NIB file and use storyboard instead? – Foriger Jun 07 '16 at 12:55
  • @Foriger - Not at this point. It's an odd omission in storyboard table views, but it is what it is. Use that kludgy hack with prototype cells if it you want, but personally, I just put up with the annoyance of the NIBs for the header/footer views. – Rob Jun 07 '16 at 15:09
  • just to be sure you mean the accepted answer is *old* for not using `dequeueReusableHeaderFooterViewWithIdentifier` and it could be written using that ie instead of using regular cells you can use cells specific to headers and footers? – mfaani Jan 10 '17 at 16:20
  • 2
    Merely saying that it is "old" is not strong enough, IMHO. The accepted answer is a kludgy way to get around the fact that not you can't have prototype cells for header and footer views. But I think it is simply wrong, because it presumes that the dequeuing of headers and footers works the same as dequeuing of cells (which the presence of a newer API for headers/footers suggests might not be the case). The accepted answer is a creative workaround that was understandable back in iOS 5, but, in iOS 6 and later, it's best to use the API explicitly designed for header/footer reuse. – Rob Oct 27 '17 at 17:19
56

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
samwize
  • 25,675
  • 15
  • 141
  • 186
  • 3
    Thank you for this answer which is NOT a hack, and the right way to do things. Also, thanks for introducing me to UITableViewHeaderFooterVIew. Btw, the guide is just excellent! :) – Roboris Feb 04 '16 at 13:50
  • Welcome. Implementing header/footer for table or collection view is not very well documented by Apple. Just yesterday I was stuck in getter header to shown when [designing via storyboad](http://samwize.com/2016/02/03/adding-header-footer-to-uicollectionview-using-storyboard/). – samwize Feb 04 '16 at 23:53
  • 2
    This should be the top rated answer for sure! – Ben Williams May 15 '16 at 20:01
  • Could you explain how to do so via storyboard, instead of xib? When doing so, I successfully dequeue, but my UILabels are null. – bunkerdive Jun 12 '17 at 21:57
22

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.

Community
  • 1
  • 1
damon
  • 1,087
  • 9
  • 12
  • 2
    You're right. I just tested that and its working, so I updated my post (http://hons82.blogspot.it/2014/05/uitableviewheader-done-right.html) containing all the possible solutions I found during my research. Thx a lot for this fix – Hons Jun 04 '14 at 20:14
  • Good Answer ... Thanks Man – Jogendra.Com Mar 15 '17 at 06:59
13

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.

barksten
  • 578
  • 4
  • 12
11

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
}
Laszlo
  • 2,803
  • 2
  • 28
  • 33
JZ.
  • 21,147
  • 32
  • 115
  • 192
  • 2
    For some reason it did not work for me untill I overrided heightForHeaderInSection also. – Entalpi Jun 17 '16 at 16:37
  • This is pretty old code. you should post another answer! – JZ. Jun 17 '16 at 18:26
  • I was stuck because I didn't know I'd have to override heightForHeaderInSection. Thanks @Entalpi – guijob Dec 11 '16 at 14:45
  • 1
    @JZ. In your Swift 3 example you deque with the following self.tableView.dequeueReusableHeaderFooterView and Swift 2: self.tableView.dequeueReusableCellWithIdentifier is there difference? – Vrutin Rathod Dec 19 '16 at 11:12
  • 1
    This saved my butt! adding a height to headerCell makes it visible. – CRey Mar 25 '17 at 17:20
  • @Vrutin I've rollback [the poor edit of November 2016](https://stackoverflow.com/review/suggested-edits/14269994) from pableiros. – Cœur Mar 31 '17 at 06:23
9

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.

Seamus
  • 3,191
  • 3
  • 22
  • 20
  • Well apple did if you use the storyboard cell as header method :) http://stackoverflow.com/questions/9219234/#11396643 – Tieme Nov 02 '12 at 12:01
  • 2
    Except that only works for prototype cells, not static cells. – Jean-Denis Muys Mar 29 '13 at 01:47
  • 1
    One really easy solution is to use [Pixate](http://www.pixate.com/); you can do quite a lot of customization without getting a reference to the header. All you have to implement is `tableView:titleForHeaderInSection`, which is a one-liner. – John Starr Dewar Apr 27 '13 at 23:06
  • 5
    That's the real solution... unfortunately its not on top, so it took me a while to find it out. I summarized the problems I had http://hons82.blogspot.it/2014/05/uitableviewheader-done-right.html – Hons May 30 '14 at 10:31
  • It's easier than that, just drag and drop. – Ricardo Jul 18 '14 at 17:55
5

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;
}
SahilS
  • 396
  • 5
  • 7
Vitalii Gozhenko
  • 9,220
  • 2
  • 48
  • 66
3

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

2

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
}
Richard Legault
  • 443
  • 4
  • 9
2

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
itsji10dra
  • 4,603
  • 3
  • 39
  • 59
Vipera Berus
  • 169
  • 1
  • 3
1

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
Community
  • 1
  • 1
MarkF
  • 952
  • 2
  • 8
  • 25
1

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;
}  
Community
  • 1
  • 1
Laszlo
  • 2,803
  • 2
  • 28
  • 33
1

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])
    }
}
CyrIng
  • 343
  • 3
  • 8
1
  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
    }
    
Martin Evans
  • 45,791
  • 17
  • 81
  • 97
leonardosccd
  • 1,503
  • 11
  • 12
0

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;
    }
    
Community
  • 1
  • 1
CedricSoubrie
  • 6,657
  • 2
  • 39
  • 44