table header view height is wrong when using auto layout, IB, and font sizes

36,502

Solution 1

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;
}

Solution 2

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;

Solution 3

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

Solution 4

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.

Solution 5

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
}
Share:
36,502
jedipixel
Author by

jedipixel

Updated on March 03, 2020

Comments

  • jedipixel
    jedipixel about 4 years

    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.

  • idStar
    idStar about 10 years
    While it would be nice for AutoLayout to just extend the height of the table header or footer based on constraints, using the above approach you've provided @vokilam, is the cleanest I've seen (and it worked for me).
  • gabriel14
    gabriel14 almost 10 years
    What helped me was reading and reassigning: UIView *header = self.tableView.tableHeaderView; and self.tableView.tableHeaderView = header;. I'm curious why.
  • kjam
    kjam over 9 years
    Is there a way to trigger execution of this code by observing autolayout rather then calling this function directly from all places that may change autolayout?
  • Josh Brown
    Josh Brown about 9 years
    Works absolutely perfectly for me - the only thing I didn't see in the answer is where to call this. I put a call in viewDidLayoutSubviews and it worked for me.
  • Josh Brown
    Josh Brown about 9 years
    Actually, it turns out that viewDidAppear is a safer place to do this, especially if you're subclassing UITableViewController. I wrote a full, more in-depth tutorial on table header view sizing in Swift that should help.
  • Martin
    Martin almost 9 years
    I like your approach, but it does not work for me... The height found is correct. But even but manually setting the header height Mr. Autolayout don't even care and enlarge my view as he wants. I hate you Mr. Autolayout.
  • Albert Bori
    Albert Bori almost 9 years
    This worked for me. It is also completely flexible across orientations, if you use an equal-widths constraint on the tableView.
  • ClockWise
    ClockWise over 8 years
    This does work for me as well...on an iPhone 6 Plus with iOS 9.1, but doesn't work on my iPhone 5s iOS 8.0, or iPhone 4s iOS 8.1. Placing this call in viewDidLayoutSubviews does really weird things, it keeps calling viewDidLayoutSubview recursively...and placing the code somewhere else doesn't work either, I've placed the code somewhere I know is called after viewDidLayoutSubviews. Any ideas?
  • Chris C
    Chris C about 8 years
    Just as a heads up, in Swift you no longer need to create a placeholder variable when changing frame values. header.frame.height = height will work fine.
  • ArpitM
    ArpitM about 8 years
    Oh cool, thanks! I updated it with just one tweak header.frame.size.height = height. Thanks for the heads up.
  • Bruno Galinari
    Bruno Galinari about 8 years
    Neat solution! Here I got a 1px-line glitch between tableHeaderView and the first section (Not even a logic 1px, an actual 1px). It turns out the height calculation was 277.5, and adding frame.size.height = round(height) did the trick.
  • Vasily
    Vasily over 7 years
    @ClockWise I found such behavior when you're using UITableViewController, because it calls layout again every time when you set tableView header. I recommend to switch to UIViewController and embedded UITableView inside it, that will eliminate the problem.
  • atulkhatri
    atulkhatri about 7 years
    I think this is the most elegant solution. Thanks a lot.
  • echappy
    echappy about 7 years
    although not the cleanest, this worked for me, was quick, and allowed me to move on with my life
  • Chris
    Chris almost 7 years
    I had to change the let height to a fixed value but this solved my problem.