There are numerous threads about correctly sizing a tableHeaderView with auto-layout (one such thread) but they tend to pre-date iOS 8.
I have a situation with numerous
Since this question is a year and a half old, here is a updated and complete version, in Swift. Some of the code from the accepted answer is wrong or outdated.
func fixHeaderHeight() {
// Set your label text if needed
// ...
//
guard let header = tableView.tableHeaderView else {
return
}
let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
header.frame.height = height
tableView.tableHeaderView = header
}
Somewhere else in your code, you'll need to set the preferredMaxLayoutWidth
of the label(s) in the header. This should be equal to the tableView (or screen width) minus any padding. The didSet
method of your label outlet is a good place:
@IBOutlet weak var headerMessageLabel: UILabel! {
didSet {
headerMessageLabel.preferredMaxLayoutWidth = UIScreen.mainScreen().bounds.width - headerMessageLabelPadding
}
}
Note: If the accepted answer worked for you, you aren't using size classes properly.
Changing your headerview function to the following works for me:
- (void)rejiggerTableHeaderView
{
self.tableView.tableHeaderView = nil;
UIView *header = self.headerView;
CGRect frame = header.frame;
frame.size.width = self.tableView.frame.size.width;
header.frame = frame;
[header setNeedsLayout];
[header layoutIfNeeded];
CGFloat height = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = header.frame;
headerFrame.size.height = height;
header.frame = headerFrame;
self.tableView.tableHeaderView = header;
}
If the header just contains a single label then I use a UILabel
extension to find a multiline label height given a width:
public extension UILabel {
public class func size(withText text: String, forWidth width: CGFloat) -> CGSize {
let measurementLabel = UILabel()
measurementLabel.text = text
measurementLabel.numberOfLines = 0
measurementLabel.lineBreakMode = .byWordWrapping
measurementLabel.translatesAutoresizingMaskIntoConstraints = false
measurementLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
let size = measurementLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return size
}
}
Note: the above is in Swift 3 syntax.
With the size
calculated above you can return the correct height in the UITableViewDelegate
method:
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
I had a same problem. This works well for me on iOS 8.4.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
[self.myLabel sizeToFit];
[self.tableView.tableHeaderView layoutIfNeeded];
return UITableViewAutomaticDimension;
}
The proper solution for this problem in Swift 3 is using this class instead of standard UILabel:
class UILabelPreferedWidth : UILabel {
override var bounds: CGRect {
didSet {
if (bounds.size.width != oldValue.size.width) {
self.setNeedsUpdateConstraints()
}
}
}
override func updateConstraints() {
if(preferredMaxLayoutWidth != bounds.size.width) {
preferredMaxLayoutWidth = bounds.size.width
}
super.updateConstraints()
}
}
Also make sure that you didn't disable those two on your Cell class:
translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
Problem can be in non-set preferredMaxLayoutWidth
.
If you will set it to correct UILabel width, it will determine constraints correctly.
You can go through all UILabel
in header and set preferredMaxLayoutWidth
to label width.
Swift 3 example:
extension UITableView {
public func relayoutTableHeaderView() {
if let tableHeaderView = tableHeaderView {
let labels = tableHeaderView.findViewsOfClass(viewClass: UILabel.self)
for label in labels {
label.preferredMaxLayoutWidth = label.frame.width
}
tableHeaderView.setNeedsLayout()
tableHeaderView.layoutIfNeeded()
tableHeaderView.frame.height = tableHeaderView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
self.tableHeaderView = tableHeaderView
}
}
public func findViewsOfClass<T:UIView>(viewClass: T.Type) -> [T] {
var views: [T] = []
for subview in subviews {
if subview is T {
views.append(subview as! T)
}
views.append(contentsOf: subview.findViewsOfClass(viewClass: T.self))
}
return views
}
}
UPDATE 2:
Also you can have problem with incorrect height calculation if you have subview with aspect ratio constraint and at the same time proportional to superview width constraint