Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Variable-height UITableView tableHeaderView with autolayout
// in a UITableViewController (or any other view controller with a UITableView)
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, 0)];
header.translatesAutoresizingMaskIntoConstraints = NO;
// [add subviews and their constraints to header]
NSLayoutConstraint *headerWidthConstraint = [NSLayoutConstraint
constraintWithItem:header attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual
toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:size.width
];
[header addConstraint:headerWidthConstraint];
CGFloat height = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
[header removeConstraint:headerWidthConstraint];
header.frame = CGRectMake(0, 0, size.width, height);
header.translatesAutoresizingMaskIntoConstraints = YES;
self.tableView.tableHeaderView = header;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self viewWillTransitionToSize:self.tableView.bounds.size withTransitionCoordinator:nil];
}
@johanforssell

This comment has been minimized.

Copy link

commented Nov 29, 2014

In iOS 7 -systemLayoutSizeFittingSize: returns size {0, 0} unless you add

        [header setNeedsLayout];
        [header layoutIfNeeded];

before [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;(that is, between rows 14 and 15).

@acalism

This comment has been minimized.

Copy link

commented Jun 10, 2015

Yes, tableHeaderView's height is variable. However, it overlaps cells totally in iOS8+,it must be ios8's bug

@benubois

This comment has been minimized.

Copy link

commented Jul 1, 2015

Swift Translation. This approach also works for tableFooterView.

Seems to work great on iOS 8.

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    self.layoutHeaderView(size.width)
}

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    self.layoutHeaderView(self.tableView.bounds.width)
}

func layoutTableHeaderView(width: CGFloat) {
    let view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 0))
    view.setTranslatesAutoresizingMaskIntoConstraints(false)

    // [add subviews and their constraints to view]

    let widthConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: width)

    view.addConstraint(widthConstraint)
    let height = view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    view.removeConstraint(constraint)

    view.frame = CGRect(x: 0, y: 0, width: width, height: height)
    view.setTranslatesAutoresizingMaskIntoConstraints(true)

    self.tableView.tableHeaderView = view
}
@jlalvarez18

This comment has been minimized.

Copy link

commented Jul 14, 2015

@benubois you rock! That worked

@robmaceachern

This comment has been minimized.

Copy link

commented Aug 5, 2015

@zkirill

This comment has been minimized.

Copy link

commented Aug 10, 2015

@marcoarment thank you!!!

I needed to add tableView.layoutIfNeeded() to viewDidLoad after registering all the nibs because otherwise tableView bounds that were returned were the frame size from IB instead of what was calculated by its own layout constraints.

@jamesbebbington

This comment has been minimized.

Copy link

commented Aug 25, 2015

Cheers @marcoarment & @benubois!

I also stumbled across this incredibly helpful explanation of the issue by @daveanderson that I though was worth sharing.

@aencarnacion

This comment has been minimized.

Copy link

commented Aug 26, 2015

Is there an example of this in a project?

@tomhamming

This comment has been minimized.

Copy link

commented Aug 15, 2016

I've filed rdar://27853770 about this. Anyone else?

@raviranderia

This comment has been minimized.

Copy link

commented Nov 11, 2016

func layoutTableHeaderView() {

    guard let headerView = tableView.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false

    let headerWidth = headerView.bounds.size.width;
    let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])

    headerView.addConstraints(temporaryWidthConstraints)

    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()

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

    frame.size.height = height
    headerView.frame = frame

    self.tableView.tableHeaderView = headerView

    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true

}
@MylesCaley

This comment has been minimized.

Copy link

commented Nov 29, 2016

Slight updates from @raviranderia to support swift 3 + as extension

  extension UITableView {
  
  func layoutTableHeaderView() {
    
    guard let headerView = self.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false
    
    let headerWidth = headerView.bounds.size.width;
    let temporaryWidthConstraints = NSLayoutConstraint.constraints(withVisualFormat: "[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])
    
    headerView.addConstraints(temporaryWidthConstraints)
    
    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()
    
    let headerSize = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
    let height = headerSize.height
    var frame = headerView.frame
    
    frame.size.height = height
    headerView.frame = frame
    
    self.tableHeaderView = headerView
    
    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true
    
  }
}
@sommestad

This comment has been minimized.

Copy link

commented Jun 16, 2017

Thanks @MylesCaley & others, works like a charm!

@MarisLagzdins

This comment has been minimized.

Copy link

commented Jun 21, 2017

Thanks guys!
This fixed a lot of problems for me!

@ghost

This comment has been minimized.

Copy link

commented Apr 6, 2018

Thanks @MylesCaley & others, works like a charm!!!

@robinclark

This comment has been minimized.

Copy link

commented Jun 15, 2018

Make sure you have an unbroken sequence of constraints from the top to the bottom of the tableHeaderView to give it height

@grifas

This comment has been minimized.

Copy link

commented Sep 11, 2018

it doesn't work with a simple label with numberOfLines set to 0 and constraints set to his superview's edges. Have you an idea how to fix that ?

EDIT: Resolved setting myLabel.translatesAutoresizingMaskIntoConstraints = false

@naqi

This comment has been minimized.

Copy link

commented Feb 19, 2019

Swift 4 updates

extension UITableView {

func layoutTableHeaderView() {
    
    guard let headerView = self.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false
    
    let headerWidth = headerView.bounds.size.width;
    
    let temporaryWidthConstraints = NSLayoutConstraint.constraints(withVisualFormat: "[headerView(width)]", options: NSLayoutConstraint.FormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])
    
    headerView.addConstraints(temporaryWidthConstraints)
    
    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()
    
    let headerSize = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
    let height = headerSize.height
    var frame = headerView.frame
    
    frame.size.height = height
    headerView.frame = frame
    
    self.tableHeaderView = headerView
    
    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true
    
}

}

@billburgess

This comment has been minimized.

Copy link

commented Mar 14, 2019

Has anyone had issues with the cell height getting calculated oddly when using a tableHeaderView or tableFooterView? My cell (when the text is close to the edge) grows too large until I scroll the footer into or out of view. Seems to only happen after an autorotation event, then gets cleared after scrolling. No footer, no problem. Can't seem to reload the other cells correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.