Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
373 views
in Technique[技术] by (71.8m points)

ios - Using autolayout in a tableHeaderView

I have a UIView subclass that contains a multi-line UILabel. This view uses autolayout.

enter image description here

I would like to set this view as the tableHeaderView of a UITableView (not a section header). The height of this header will depend on the text of the label, which in turn depends on the width of the device. The sort of scenario autolayout should be great at.

I have found and attempted many many solutions to get this working, but to no avail. Some of the things I've tried:

  • setting a preferredMaxLayoutWidth on each label during layoutSubviews
  • defining an intrinsicContentSize
  • attempting to figure out the required size for the view and setting the tableHeaderView's frame manually.
  • adding a width constraint to the view when the header is set
  • a bunch of other things

Some of the various failures I've encountered:

  • label extends beyond the width of the view, doesn't wrap
  • frame's height is 0
  • app crashes with exception Auto Layout still required after executing -layoutSubviews

The solution (or solutions, if necessary) should work for both iOS 7 and iOS 8. Note that all of this is being done programmatically. I've set up a small sample project in case you want to hack on it to see the issue. I've reset my efforts to the following start point:

SCAMessageView *header = [[SCAMessageView alloc] init];
header.titleLabel.text = @"Warning";
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.";
self.tableView.tableHeaderView = header;

What am I missing?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

My own best answer so far involves setting the tableHeaderView once and forcing a layout pass. This allows a required size to be measured, which I then use to set the frame of the header. And, as is common with tableHeaderViews, I have to again set it a second time to apply 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;
}

For multiline labels, this also relies on the custom view (the message view in this case) setting the preferredMaxLayoutWidth of each:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
    self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.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()
let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
var frame = header.frame
frame.size.height = height
header.frame = frame
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
        header.setNeedsLayout()
        header.layoutIfNeeded()
        let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var frame = header.frame
        frame.size.height = height
        header.frame = frame
        self.tableHeaderView = header
    }
}

Usage:

let header = SCAMessageView()
header.titleLabel.text = "Warning"
header.subtitleLabel.text = "Warning message here."
tableView.setAndLayoutTableHeaderView(header)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...