34
votes

I am trying to create a header view for my uiTableView (not a section header, I already have those.) I have set up an XIB in interface builder. All the connections are hooked up and it runs beautifully... except the table doesn't give it enough room! My problem is that the top of the table overlaps the table's header by a little.

My XIB is setup with autlayout for all the buttons, and IB is happy that the constraints don't conflict / ambiguous. The view is set to Freeform size, which in my case ended up being 320 x 471. Then in constraints for the view, I set an intrinsic size for the view of the same.

Now this works perfectly with my table. Everything looks great. But if I manually change any of the fonts in the header view with code, the layout makes the view bigger, and it ends up underneath my table.

Any ideas how to get the tableviewcontroller to leave enough room for the header view after setting fonts and sizes? I hope I've made sense explaining this.

11
any chance of a screen grab? - Max MacLeod
Maybe this solves the problem: stackoverflow.com/a/16324668/570339 - Ramy Al Zuhouri
The header view is only sized wrong if I change the font and size of various UI elements in the viewcontroller code. screenshots available. The header of the table ends with the large photo. then the table begins - jedipixel
I am fiddling with my autolayout constraints in case maybe they're the problem. They all compile without warnings, but maybe theres a setting I've missed. - jedipixel
I am wrestling with this issue as well. My header view is supposed to expand to the height of its contents, but it does not. Did you ever find a solution? - Greg W

11 Answers

65
votes

Note: A Swift 3+ version can be found here: https://gist.github.com/marcoarment/1105553afba6b4900c10#gistcomment-1933639


The idea is to calculate header's height with help of systemLayoutSizeFittingSize:targetSize.

Returns the size of the view that satisfies the constraints it holds. Determines the best size of the view considering all constraints it holds and those of its subviews.

After changing header's height it is necessary to reassign tableHeaderView property to adjust table cells.

Based on this answer: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

- (void)sizeHeaderToFit
{
    UIView *header = self.tableView.tableHeaderView;

    [header setNeedsLayout];
    [header layoutIfNeeded];

    CGFloat height = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    CGRect frame = header.frame;

    frame.size.height = height;
    header.frame = frame;

    self.tableView.tableHeaderView = header;
}
14
votes

I encountered a similar problem when I was trying to set my table view header to a custom view I defined with auto layout using interface builder.

When I did so, I would find that it would be obscured by a section header.

My work around involved using a "dummy view" as the table header view and then adding my custom view to that dummy view as a subview. I think this allows auto layout to configure the appearance as per the constraints and the containing view's frame. This is probably not as elegant as vokilam's solution, but it works for me.


CGRect headerFrame = CGRectMake(0, 0, yourWidth, yourHeight);
UIView *tempView = [[UIView alloc] initWithFrame:headerFrame];
[tempView setBackgroundColor:[UIColor clearColor]];
YourCustomView *customView = [[YourCustomView alloc] initWithFrame: headerFrame];
[tempView addSubview:movieHeader];
self.tableView.tableHeaderView = tempView;
9
votes

Because your tableHeaderView is inited form xib with auto layout, so the constraints of your custom headerView and the superView is unknown.You should add the constraints of your custom headerView:

1.in viewDidLLoad assign your custom headerView to the tableView's tableHeaderView

 - (void)viewDidLoad
{
    [super viewDidLoad];
    UIView *yourHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"yourHeaderView" owner:nil options:nil] objectAtIndex:0];
    //important:turn off the translatesAutoresizingMaskIntoConstraints;apple documents for details
     yourHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
    self.tableView.tableHeaderView = yourHeaderView;
}

2.in - (void)updateViewConstraints,add the essential constraints of your custom headerView

- (void)updateViewConstraints
{
    NSDictionary *viewsDictionary = @{@"headerView":yourHeaderView};

    NSArray *constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[headerView(121)]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];

    NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[headerView(320)]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];
    [self.headerView addConstraints:constraint_H];
    [self.headerView addConstraints:constraint_V];

    NSArray *constraint_POS_V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[headerView]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];

    NSArray *constraint_POS_H = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerView]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];
    [self.tableView addConstraints:constraint_POS_V];
    [self.tableView addConstraints:constraint_POS_H];

    [super updateViewConstraints];
}

OK! PS:Here is the related document:Resizing the View Controller’s Views

8
votes

What solved my issue was this:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    sizeHeaderToFit()
}

private func sizeHeaderToFit() { 
    if let headerView = tableView.tableHeaderView {

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

        let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var newFrame = headerView.frame

        // Needed or we will stay in viewDidLayoutSubviews() forever
        if height != newFrame.size.height {
            newFrame.size.height = height
            headerView.frame = newFrame

            tableView.tableHeaderView = headerView
        }
    }
}

I found this solution somewhere and worked like charm, I can't remember where though.

3
votes

I found vokilam's answer to work great. Here's his solution in Swift.

func sizeHeaderToFit() {
    guard let header = tableView.tableHeaderView else { return }
    header.setNeedsLayout()
    header.layoutIfNeeded()
    let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    header.frame.size.height = height
    tableView.tableHeaderView = header
}
1
votes

In my case systemLayoutSizeFittingSize return incorrect size, because one of the subviews use aspect ratio (width to height) 4:1 constraint.

After I change it to constant height constraint value (100), it begin to works as expected.

1
votes

Combining answers by @SwiftArchitect, @vokilam, and @mayqiyue, and using a programmatically created tableHeaderView instead of a nib (although, I don't see any reason why it shouldn't work with a nib), and under iOS 10.x/Xcode 8.2.1, here is what I have working:

Create the view in your view controller's -viewDidLoad or -init methods, or wherever you're using the table view:

MyCustomTableHeaderView *myCustomTableHeaderView = [[MyCustomTableHeaderView alloc] init]; // Don't set its frame
myCustomTableHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
self.myTableView.tableHeaderView = myCustomTableHeaderView;
self.myCustomTableHeaderView = myCustomTableHeaderView;  // We set and update our constraints in separate methods so we store the table header view in a UIView property to access it later

Wherever you set up your custom header view's constraints (could be in `updateConstraints but this method could be called multiple times):

// We use Pure Layout in our project, but the workflow is the same:
// Set width and height constraints on your custom view then pin it to the top and left of the table view
// Could also use VFL or NSLayoutConstraint methods
[self.myCustomTableHeaderView autoSetDimension:ALDimensionWidth toSize:CGRectGetWidth(self.superview.frame)]; // Width set to tableview's width
[self.myCustomTableHeaderView autoSetDimension:ALDimensionHeight toSize:height]; // Whatever you want your height to be
[self.myCustomTableHeaderView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[self.myCustomTableHeaderView autoPinEdgeToSuperviewEdge:ALEdgeLeft];

// This is important
[self.myCustomTableHeaderView layoutIfNeeded]; // We didn't need to call -setNeedsLayout or reassign our custom header view to the table view's tableHeaderView property again, as was noted in other answers
1
votes

The other answers pointed me in the right direction for getting the tableHeaderView to resize appropriately. However, I was getting a gap between the header and the tableView cells. This could also cause your cells to overlap with your header, depending on if the header grew or got smaller.

I went from this:

 tableView.reloadData()
 let height = tableViewHeader.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
 tableView.tableHeaderView?.frame.size.height = height

To fix the gap/overlap all I had to do was move tableView.reloadData() to after I set the new height instead of before.

To this:

 let height = tableViewHeader.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
 tableView.tableHeaderView?.frame.size.height = height
 tableView.reloadData()
0
votes

sizeHeaderToFit suggested by @vokilam didn't work for me.

What worked is a modified version of @mayqiyue, invoked during viewDidLoad, wired height, equal width to table view.

// Anchor the headerview, and set width equal to superview
NSDictionary *viewsDictionary = @{@"headerView":self.tableView.tableHeaderView,
                                  @"tableView":self.tableView};

[self.tableView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[headerView]"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:viewsDictionary]];
[self.tableView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerView]"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:viewsDictionary]];
[self.tableView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[headerView(==tableView)]"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:viewsDictionary]];
[self.tableView.tableHeaderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[headerView(121)]"
                                                                                       options:0
                                                                                       metrics:nil
                                                                                         views:viewsDictionary]];
0
votes

Below code has worked for me :- Add this in your UIView class -

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];

if (self) {
    self.frame = CGRectMake(0, 0, self.frame.size.width, [[self class] height]);
}

return self;

}

0
votes

@rmigneco answer worked for me, so here's the swift version of it:

let headerFrame = CGRectMake(0, 0, 100, 1);
let tempView = UIView(frame: headerFrame);
tempView.backgroundColor = UIColor.clearColor()

let yourCustomView = CustomView(frame: headerFrame)
tempView.addSubview(yourCustomView)

self.tableView.tableHeaderView = tempView