在tableHeaderView中使用autolayout

Ben*_*ard 16 uitableview uiview ios autolayout nslayoutconstraint

我有一个UIView包含多行的子类UILabel.此视图使用autolayout.

在此输入图像描述

我想设置该视图为tableHeaderViewUITableView(不是一节头).此标题的高度取决于标签的文本,而标签的文本又取决于设备的宽度.那种场景自动布局应该很棒.

我发现并尝试 许多 解决方案来实现这一点,但无济于事.我试过的一些事情:

  • preferredMaxLayoutWidth在期间为每个标签设置一个layoutSubviews
  • 定义一个 intrinsicContentSize
  • 试图找出视图所需的大小并tableHeaderView手动设置框架.
  • 设置标头时向视图添加宽度约束
  • 一堆其他的东西

我遇到的一些各种失败:

  • 标签超出视图的宽度,不包裹
  • 帧的高度为0
  • 应用程序崩溃异常 Auto Layout still required after executing -layoutSubviews

解决方案(或解决方案,如有必要)应适用于iOS 7和iOS 8.请注意,所有这些都是以编程方式完成的.我已经设置了一个小型示例项目,以防您想要查看问题.我已将我的努力重置到以下起点:

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;
Run Code Online (Sandbox Code Playgroud)

我错过了什么?

Ben*_*ard 21

到目前为止,我自己的最佳答案涉及设置tableHeaderView一次并强制布局传递.这允许测量所需的大小,然后我用它来设置标题的框架.并且,与tableHeaderViews一样,我必须再次设置它以应用更改.

- (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;
}
Run Code Online (Sandbox Code Playgroud)

对于多行标签,这也依赖于自定义视图(在本例中为消息视图)设置preferredMaxLayoutWidth每个标签:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
    self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
}
Run Code Online (Sandbox Code Playgroud)

2015年1月更新

不幸的是,这仍然是必要的 这是布局过程的一个快速版本:

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
Run Code Online (Sandbox Code Playgroud)

我发现将它移动到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
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

let header = SCAMessageView()
header.titleLabel.text = "Warning"
header.subtitleLabel.text = "Warning message here."
tableView.setAndLayoutTableHeaderView(header)
Run Code Online (Sandbox Code Playgroud)


aun*_*nnn 9

对于仍在寻找解决方案的人来说,这适用于Swift 3和iOS 9+.这是仅使用AutoLayout的一个.它还可以在设备旋转时正确更新.

extension UITableView {
    // 1.
    func setTableHeaderView(headerView: UIView) {
        headerView.translatesAutoresizingMaskIntoConstraints = false

        self.tableHeaderView = headerView

        // ** Must setup AutoLayout after set tableHeaderView.
        headerView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
        headerView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
        headerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    }

    // 2.
    func shouldUpdateHeaderViewFrame() -> Bool {
        guard let headerView = self.tableHeaderView else { return false }
        let oldSize = headerView.bounds.size        
        // Update the size
        headerView.layoutIfNeeded()
        let newSize = headerView.bounds.size
        return oldSize != newSize
    }
}
Run Code Online (Sandbox Code Playgroud)

使用:

override func viewDidLoad() {
    ...

    // 1.
    self.tableView.setTableHeaderView(headerView: customView)
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    // 2. Reflect the latest size in tableHeaderView
    if self.tableView.shouldUpdateHeaderViewFrame() {

        // **This is where table view's content (tableHeaderView, section headers, cells) 
        // frames are updated to account for the new table header size.
        self.tableView.beginUpdates()
        self.tableView.endUpdates()
    }
}
Run Code Online (Sandbox Code Playgroud)

要点是,您应该以与表格视图单元格相同的方式tableView管理框架tableHeaderView.这是通过tableView's 完成的beginUpdates/endUpdates.

事情是tableView它更新子帧时不关心AutoLayout.它使用当前 tableHeaderView大小来确定第一个单元/节头应该在哪里.

1)添加宽度约束,以便tableHeaderView在调用layoutIfNeeded()时使用此宽度.同时添加centerX和top限制以相对于它正确定位它tableView.

2)为了让人tableView知道最新的大小tableHeaderView,例如,当设备被旋转时,在viewDidLayoutSubviews中我们可以调用layoutIfNeeded()tableHeaderView.然后,如果更改了大小,请调用beginUpdates/endUpdates.

请注意,我没有在一个函数中包含beginUpdates/endUpdates,因为我们可能希望稍后将调用推迟.

查看示例项目