Is it possible to use AutoLayout with UITableView's tableHeaderView?
Solution 1
I asked and answered a similar question here. In summary, I add the header once and use it to find the required height. That height can then be applied to the header, and the header is set a second time to reflect the change.
- (void)viewDidLoad
{
[super viewDidLoad];
self.header = [[SCAMessageView alloc] init];
self.header.titleLabel.text = @"Warning";
self.header.subtitleLabel.text = @"This is a message with enough text to span multiple lines. This text is set at runtime and might be short or long.";
//set the tableHeaderView so that the required height can be determined
self.tableView.tableHeaderView = self.header;
[self.header setNeedsLayout];
[self.header layoutIfNeeded];
CGFloat height = [self.header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
//update the header's frame and set it again
CGRect headerFrame = self.header.frame;
headerFrame.size.height = height;
self.header.frame = headerFrame;
self.tableView.tableHeaderView = self.header;
}
If you have multi-line labels, this also relies on the custom view setting the preferredMaxLayoutWidth of each label:
- (void)layoutSubviews
{
[super layoutSubviews];
self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
}
or perhaps more generally:
override func layoutSubviews() {
super.layoutSubviews()
for view in subviews {
guard let label = view as? UILabel where label.numberOfLines == 0 else { continue }
label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame)
}
}
Update January 2015
Unfortunately this still seems necessary. Here is a swift version of the layout process:
tableView.tableHeaderView = header
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
tableView.tableHeaderView = header
I've found it useful to move this into an extension on UITableView:
extension UITableView {
//set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
func setAndLayoutTableHeaderView(header: UIView) {
self.tableHeaderView = header
self.tableHeaderView?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
header.widthAnchor.constraint(equalTo: self.widthAnchor)
])
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size = header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
self.tableHeaderView = header
}
}
Usage:
let header = SCAMessageView()
header.titleLabel.text = "Warning"
header.subtitleLabel.text = "Warning message here."
tableView.setAndLayoutTableHeaderView(header)
Solution 2
I've been unable to add a header view using constraints (in code). If I give my view a width and/or a height constraint, I get a crash with the message saying:
"terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."
When I add a view in the storyboard to my table view, it shows no constraints, and it works fine as a header view, so I think that the placement of the header view isn't done using constraints. It doesn't seem to behave like a normal view in that regard.
The width is automatically the width of the table view, the only thing you need to set is the height -- the origin values are ignored, so it doesn't matter what you put in for those. For instance, this worked fine (as does 0,0,0,80 for the rect):
UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)];
headerview.backgroundColor = [UIColor yellowColor];
self.tableView.tableHeaderView = headerview;
Solution 3
I saw a lot of methods here doing so much unnecessary stuff, but you don't need that much to use auto layout in the header view. You just have to create you xib file, put your constraints and instantiate it like this:
func loadHeaderView () {
guard let headerView = Bundle.main.loadNibNamed("CourseSearchHeader", owner: self, options: nil)?[0] as? UIView else {
return
}
headerView.autoresizingMask = .flexibleWidth
headerView.translatesAutoresizingMaskIntoConstraints = true
tableView.tableHeaderView = headerView
}
Solution 4
Another solution is to dispatch the header view creation to the next main thread call:
- (void)viewDidLoad {
[super viewDidLoad];
// ....
dispatch_async(dispatch_get_main_queue(), ^{
_profileView = [[MyView alloc] initWithNib:@"MyView.xib"];
self.tableView.tableHeaderView = self.profileView;
});
}
Note: It fix the bug when the loaded view has a fixed height. I haven't tried when the header height only depends on its content.
EDIT :
You can find a cleaner solution to this problem by implementing this function, and calling it in viewDidLayoutSubviews
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self sizeHeaderToFit];
}
Solution 5
Updated for Swift 4.2
extension UITableView {
var autolayoutTableViewHeader: UIView? {
set {
self.tableHeaderView = newValue
guard let header = newValue else { return }
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size =
header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
self.tableHeaderView = header
}
get {
return self.tableHeaderView
}
}
}
Related videos on Youtube
Comments
-
ItsASecret over 2 years
Since I discovered
AutoLayout
I use it everywhere, now I'm trying to use it with atableHeaderView
.I made a
subclass
ofUIView
added everything (labels etc...) I wanted with their constraints, then I added thisCustomView
to theUITableView
'tableHeaderView
.Everything works just fine except the
UITableView
always displays above theCustomView
, by above I mean theCustomView
is under theUITableView
so it can't be seen !It seems that no matter what I do, the
height
of theUITableView
'tableHeaderView
is always 0 (so is the width, x and y).My question : is it possible at all to accomplish this without setting the frame manually ?
EDIT : The
CustomView
'subview
that I'm using has these constraints :_title = [[UILabel alloc]init]; _title.text = @"Title"; [self addSubview:_title]; [_title keep:[KeepTopInset rules:@[[KeepEqual must:5]]]]; // title has to stay at least 5 away from the supperview Top [_title keep:[KeepRightInset rules:@[[KeepMin must:5]]]]; [_title keep:[KeepLeftInset rules:@[[KeepMin must:5]]]]; [_title keep:[KeepBottomInset rules:@[[KeepMin must:5]]]];
I'm using a handy library 'KeepLayout' because writing constraints manually takes forever and way too many line for one single constraint but the methods are self-explaining.
And the
UITableView
has these constraints :_tableView = [[UITableView alloc]init]; _tableView.translatesAutoresizingMaskIntoConstraints = NO; _tableView.delegate = self; _tableView.dataSource = self; _tableView.backgroundColor = [UIColor clearColor]; [self.view addSubview:_tableView]; [_tableView keep:[KeepTopInset rules:@[[KeepEqual must:0]]]];// These 4 constraints make the UITableView stays 0 away from the superview top left right and bottom. [_tableView keep:[KeepLeftInset rules:@[[KeepEqual must:0]]]]; [_tableView keep:[KeepRightInset rules:@[[KeepEqual must:0]]]]; [_tableView keep:[KeepBottomInset rules:@[[KeepEqual must:0]]]]; _detailsView = [[CustomView alloc]init]; _tableView.tableHeaderView = _detailsView;
I don't know if I have to set some constraints directly on the
CustomView
, I think the height of the CustomView is determined by the constraints on theUILabel
"title" in it.EDIT 2: After another investigation it seems the height and width of the CustomView are correctly calculated, but the top of the CustomView is still at the same level than the top of the UITableView and they move together when I scroll.
-
jrturton almost 11 yearsYes, it is possible. Can you show the code you're using? It's difficult to advise without knowing what constraints you have set up on the header view.
-
Mariam K. almost 11 yearsAn easy way for you to accomplish this is to add that view in IB to the tableView..just create the view in the same scene containing the tableview and drag it to the table.
-
ItsASecret almost 11 yearsI'm trying to avoid IB the most I can, so far I didn't have to use it, if I can't get it to work I'll try with IB
-
Mariam K. almost 11 yearsApple advises developers to use IB whenever possible when it comes to autolayout. It really helps in avoiding a lot of inconsistency problems.
-
malex almost 8 yearsThe true complete autolayout solution is here
-
Elijah almost 6 yearsThis answer solves this very eloquently. stackoverflow.com/questions/34661793/…
-
Schemetrical almost 5 yearsmedium.com/@aunnnn/… This solves this issue almost perfectly.
-
-
ItsASecret almost 11 yearsI had that exception too but adding a category to UITableView fixed it I found it in that answer : stackoverflow.com/questions/12610783/…
-
ItsASecret almost 11 yearsI'm still gonna try what you suggest, but tomorrow morning, it's 1:34 am I'm going to bed, thank you very much for taking the time to answer ! (But I really want to not specify a height, I would like it to be calculated by the constraints I set up on the label in the CustomView)
-
ItsASecret almost 11 yearsI've tried it and yeah setting the frame works, but I was looking for a way to avoid setting the frame, I'll keep looking and if I find nothing else I'll accept your answer
-
Rpranata over 9 yearsquestion is about
tableHeaderView
not the section header. -
ItsASecret over 9 yearsI can't really test this now since it's been a year and a half and 2 major iOS have been released since, but if this works then this seems to be what I wanted to do!
-
Ben Packard over 9 yearsYeah, just figured I should add it for posterity.
-
Nik about 9 yearsWorked for me after I removed my header from storyboard (from table view) and placed it into separate xib
-
Benjohn almost 9 yearsI get this exception (currently testing 7.1) if the added header view has
translatesAutoresizingMaskIntoConstraints = NO
. Turning translation on prevents the error – I suspectUITableView
as of 7.1 doesn't attempt to autolayout its header view and wants something with the frame pre-set. -
Benjohn almost 9 yearsAn alternative to using
preferredMaxLayoutWidth
is adding a width constraint (equal to the table view's width) on the header view prior to usingsystemLayoutSizeFittingSize:
. -
Laszlo over 8 yearsNOTE: if you experiencing that the header is above the first cells, then you forgot to reset the header property to
self.tableView.tableHeaderView
-
Martin over 8 years@TussLaszlo
tableHeaderView
are kind of buggy with autolayout. There is some workarounds, like this one. But since I wrote this, i've found a better and cleaner solution here stackoverflow.com/a/21099430/127493 by calling its- (void)sizeHeaderToFit
inviewDidLayoutSubviews
-
TylerJames over 8 yearsOften it amazes me how tricky it can be to do something completely trivial like this.
-
HotJard over 7 yearsJesus! label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame) is very important! Thanks
-
docchang over 7 yearsSpent a day yesterday trying to get the tableHeader to auto resize/layout correctly. This solution works for me. Thanks a bunch.
-
JakubKnejzlik over 7 yearsNOTE: If you need to get exact width as tableView, you should get height with required horizontal priority
let height = header.systemLayoutSizeFittingSize(CGSizeMake(CGRectGetWidth(self.bounds), 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
-
Daumantas Versockas over 7 years@Ben Packard, it seems, that this work around doesn't work on iOS 10 anymore. The final line
self.tableHeaderView = header
sets the header with the correct frame. Despite this, inside theviewDidLayoutSubviews
frame is incorrect (frame = (0 0; 375 0);
) -
Kesong Xie about 7 yearsJust Use
header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFitting(UILayoutFittingCompressedSize) self.tableHeaderView = header
would work at iOS 10.2 -
Simon about 7 yearsThis loops for me in iOS 10.
-
smirkingman over 6 yearsThis also worked for us on iOS 11 with a dynamic height header with multi-line labels.
-
d4Rk over 6 yearsYou can also remove the flexibleHeight-Autoresizing-Option in IB of course.
-
abhimuralidharan about 6 yearsIt works.Give proper autolayout constraints to all the tableheaderview subviews. If you miss a single constraint, it will not work.
-
marvin_yorke almost 6 yearsHi! Could you please explain
self.tableFooterView.transform
part? Why is it necessary? -
k06a almost 6 years@mrvn transform is used to move footer to the bottom of the tableView.
-
dinesharjani almost 5 yearsI didn't do this exactly but you gave me a good idea - removing the headerView, re-setting its frame, and adding it back.
-
tounaobun almost 5 yearsPlus for adding multi-line label handle cases.
-
vikzilla almost 5 yearsI've been trying to set the height of my tableFooterView (via a xib / nib) and wasn't having success setting the frame, height, layoutIfNeeded(), etc. But this solution finally allowed me to set it.
-
Denis Kutlubaev about 4 yearsDon't forget to set height constraint to whole view in a xib file.
-
user over 3 yearsNice. Autoresize masks are heavily underrated.
-
Dale about 3 yearsThis is all that is required! In fact you can leave out both topAnchor and centerXAnchor too