Is it possible to use AutoLayout with UITableView's tableHeaderView?

Since I discovered AutoLayout I use it everywhere, now I'm trying to use it with a tableHeaderView.

I made a subclass of UIView added everything (labels etc...) I wanted with their constraints, then I added this CustomView to the UITableView'tableHeaderView.

Everything works just fine except the UITableView always displays above the CustomView, by above I mean the CustomView is under the UITableView so it can't be seen !

It seems that no matter what I do, the height of the UITableView'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 the UILabel "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.

82453 次浏览

我无法使用约束(在代码中)添加头视图。如果我给视图一个宽度和/或高度限制,我会得到一个崩溃的消息:

 "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;

My table header view is a UIView subclass - I created a single contentView UIView within the initializer, with its bounds the same as the table header view's frame and added all my objects as a subview of that.

然后在表头视图的 layoutSubviews方法中而不是在初始值设定项中添加对象的约束。解决了车祸。

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:CGRectMake(0, 0, 0, 44.0)];
if (self) {
UIView *contentView = [[UIView alloc] initWithFrame:self.bounds];
contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth;


// add other objects as subviews of content view


}
return self;
}


- (void)layoutSubviews
{
[super layoutSubviews];


// remake constraints here
}

接受的答案是一个唯一有用的表与一个单一的部分。对于多节 UITableView只要确保您的头继承自 UITableViewHeaderFooterView,您将罚款。

作为一种替代方法,只需将当前的头嵌入到 UITableViewHeaderFooterViewcontentView中。

通过使用 尺寸方法,您可以获得自动布局以提供大小。

You can then use this to create the frame for your application. This technique works whenever you need to know the size of a view that uses autolayout internally.

Swift 中的代码看起来像

//Create the view
let tableHeaderView = CustomTableHeaderView()


//Set the content
tableHeaderView.textLabel.text = @"Hello world"


//Ask auto layout for the smallest size that fits my constraints
let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)


//Create a frame
tableHeaderView.frame = CGRect(origin: CGPoint.zeroPoint, size: size)


//Set the view as the header
self.tableView.tableHeaderView = self.tableHeaderView

或者在 Objective-C 中

//Create the view
CustomTableHeaderView *header = [[CustomTableHeaderView alloc] initWithFrame:CGRectZero];


//Set the content
header.textLabel.text = @"Hello world";


//Ask auto layout for the smallest size that fits my constraints
CGSize size = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];


//Create a frame
header.frame = CGRectMake(0,0,size.width,size.height);


//Set the view as the header
self.tableView.tableHeaderView = header

还应该注意的是,在这个特殊的实例中,在子类中覆盖 requresConstraintBasedLayout 确实会导致执行布局传递,但是这个布局传递的结果被忽略,系统框架设置为 tableView 的宽度和0高度。

I asked and answered a similar question 给你. 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;
}

如果您有多行标签,这也依赖于自定义视图设置每个标签的首选 MaxLayoutWidth:

- (void)layoutSubviews
{
[super layoutSubviews];


self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
}

或者更一般地说:

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)
}
}

2015年1月更新

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

tableView.tableHeaderView = header
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
tableView.tableHeaderView = header

我发现把它移到 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
}
}

用法:

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

另一种解决方案是将头视图创建分派到下一个主线程调用:

- (void)viewDidLoad {
[super viewDidLoad];


// ....


dispatch_async(dispatch_get_main_queue(), ^{
_profileView = [[MyView alloc] initWithNib:@"MyView.xib"];
self.tableView.tableHeaderView = self.profileView;
});
}

注意: 当加载的视图具有固定的高度时,它会修复 bug。我还没有尝试当标题高度只取决于它的内容。

EDIT :

您可以通过实现这个 功能并在 viewDidLayoutSubviews中调用它来找到这个问题的更清晰的解决方案

- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];


[self sizeHeaderToFit];
}

我知道这是一个旧的职位,但通过所有的职位关于这一点,通过整个下午玩这个,我终于想出了一个干净,但非常简单的解决方案

首先,我的视图层次结构如下:

  1. 表格视图
    1. 查看 tableHeaderView
      1. 查看 使用一个名为 < strong > headerView 的插座

现在在 View (No. 3)中,我设置了所有的约束,正如我通常会包括容器的底部空间一样。这会令容器(即3。视图(即 headerView)根据子视图及其约束来调整自身大小。

After that, I set the constraints between 3. View and 2. View to these:

  1. 顶部空间到容器: 0
  2. 引导空间到容器: 0
  3. 到集装箱的拖尾空间: 0

注意,我故意省略了底部空间。

一旦所有这些都在故事板中完成,剩下要做的就是粘贴这三行代码:

if (self.headerView.frame.size.height != self.tableView.tableHeaderView.frame.size.height) {
UIView *header = self.tableView.tableHeaderView;
CGRect frame = self.tableView.tableHeaderView.frame;
frame.size.height = self.headerView.frame.size.height + frame.origin.y;
header.frame = frame;
self.tableView.tableHeaderView = header;
}

Code:

  extension UITableView {


func sizeHeaderToFit(preferredWidth: CGFloat) {
guard let headerView = self.tableHeaderView else {
return
}


headerView.translatesAutoresizingMaskIntoConstraints = false
let layout = NSLayoutConstraint(
item: headerView,
attribute: .Width,
relatedBy: .Equal,
toItem: nil,
attribute:
.NotAnAttribute,
multiplier: 1,
constant: preferredWidth)


headerView.addConstraint(layout)


let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
headerView.frame = CGRectMake(0, 0, preferredWidth, height)


headerView.removeConstraint(layout)
headerView.translatesAutoresizingMaskIntoConstraints = true


self.tableHeaderView = headerView
}
}

下面这些对我很有用。

  1. Use a plain old UIView as the header view.
  2. 将子视图添加到 UIView
  3. 在子视图上使用自动布局

我看到的主要好处是限制帧计算。苹果应该真正更新 UITableView的 API,使这更容易。

使用 SnapKit 的例子:

let layoutView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 60))
layoutView.backgroundColor = tableView.backgroundColor
tableView.tableHeaderView = layoutView


let label = UILabel()
layoutView.addSubview(label)
label.text = "I'm the view you really care about"
label.snp_makeConstraints { make in
make.edges.equalTo(EdgeInsets(top: 10, left: 15, bottom: -5, right: -15))
}

奇怪的事情总会发生。SystemLayoutSizeFittingSize 对于 iOS9来说非常好用,但是对于我的 iOS8来说就不是这样了。所以这个问题很容易解决。通过插入高度为 CGRectGetMaxY (yourview.frame) + 填充,只需在超级调用更新头部视图界限后,在头部和 viewDidLayoutSubviews 中获取到底部视图的链接

UPD: The easiest solution ever: 因此,在标题视图中放置子视图,并将其别针到 左边头儿。在该子视图中放置具有自动高度约束的子视图。然后将所有作业交给自动布局(不需要计算)

- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];


CGFloat height = CGRectGetMaxY(self.tableView.tableHeaderView.subviews.firstObject.frame);
self.tableView.tableHeaderView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);
self.tableView.tableHeaderView = self.tableView.tableHeaderView;
}

结果子视图像它应该的那样扩展/收缩,最后它调用 viewDidLayoutSubviews。当时我们知道视图的实际大小,所以设置 headerView 高度并通过重新分配来更新它。 Works like a charm!

也适用于页脚视图。

提示: 如果你使用 setAndLayoutTableHeaderView 方法,你应该更新子视图的框架,所以在这种情况下 UILabel 的首选 MaxLayoutWidth 应该在 systemLayoutSizeFittingSize 调用之前调用,不要调用 layoutSubview。

code show

Extended this solution http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/ for table footer view:

@interface AutolayoutTableView : UITableView


@end


@implementation AutolayoutTableView


- (void)layoutSubviews {
[super layoutSubviews];


// Dynamic sizing for the header view
if (self.tableHeaderView) {
CGFloat height = [self.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = self.tableHeaderView.frame;


// If we don't have this check, viewDidLayoutSubviews() will get
// repeatedly, causing the app to hang.
if (height != headerFrame.size.height) {
headerFrame.size.height = height;
self.tableHeaderView.frame = headerFrame;
self.tableHeaderView = self.tableHeaderView;
}


[self.tableHeaderView layoutIfNeeded];
}


// Dynamic sizing for the footer view
if (self.tableFooterView) {
CGFloat height = [self.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect footerFrame = self.tableFooterView.frame;


// If we don't have this check, viewDidLayoutSubviews() will get
// repeatedly, causing the app to hang.
if (height != footerFrame.size.height) {
footerFrame.size.height = height;
self.tableFooterView.frame = footerFrame;
self.tableFooterView = self.tableFooterView;
}


self.tableFooterView.transform = CGAffineTransformMakeTranslation(0, self.contentSize.height - footerFrame.size.height);
[self.tableFooterView layoutIfNeeded];
}
}


@end

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
}

分享我的方法。

UITableView+XXXAdditions.m

- (void)xxx_setTableHeaderView:(UIView *)tableHeaderView layoutBlock:(void(^)(__kindof UIView *tableHeaderView, CGFloat *containerViewHeight))layoutBlock {
CGFloat containerViewHeight = 0;
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
[backgroundView addSubview:tableHeaderView];
layoutBlock(tableHeaderView, &containerViewHeight);


backgroundView.frame = CGRectMake(0, 0, 0, containerViewHeight);


self.tableHeaderView = backgroundView;
}

用法。

[self.tableView xxx_setTableHeaderView:myView layoutBlock:^(__kindof UIView * _Nonnull tableHeaderView, CGFloat *containerViewHeight) {
*containerViewHeight = 170;


[tableHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@20);
make.centerX.equalTo(@0);
make.size.mas_equalTo(CGSizeMake(130, 130));
}];
}];

一封旧邮件。但是一封好邮件。这是我的2分钱。

首先,确保页眉视图有其约束条件,以便它能够支持自己的内部内容大小。然后做下面的事情。

//ViewDidLoad
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.configure(title: "Some Text A")


//Somewhere else
headerView.update(title: "Some Text B)


private var widthConstrained = false


override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if widthConstrained == false {
widthConstrained = true
tableView.addConstraint(NSLayoutConstraint(item: headerView, attribute: .width, relatedBy: .equal, toItem: tableView, attribute: .width, multiplier: 1, constant: 0))
headerView.layoutIfNeeded()
tableView.layoutIfNeeded()
}
}


override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
self.headerView.layoutIfNeeded()
self.tableView.layoutIfNeeded()
}, completion: nil)
}

您可以在头和表视图之间添加 top + 横向位置约束,以正确地放置它(如果头本身包含所有必要的内部布局约束以拥有正确的帧)

在 tableViewController viewDidLoad 方法中

    headerView.translatesAutoresizingMaskIntoConstraints = false


tableView.tableHeaderView = headerView


headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true
headerView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true

我能够通过以下方法实现它(这对页脚同样适用)。

First, you will need small UITableView extension:

Swift 3

extension UITableView {
fileprivate func adjustHeaderHeight() {
if let header = self.tableHeaderView {
adjustFrame(header)
}
}


private func adjustFrame(_ view: UIView) {
view.frame.size.height = calculatedViewHeight(view)
}


fileprivate func calculatedHeightForHeader() -> CGFloat {
if let header = self.tableHeaderView {
return calculatedViewHeight(header)
}
return 0.0
}


private func calculatedViewHeight(_ view: UIView) -> CGFloat {
view.setNeedsLayout()
let height = view.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
return height
}
}

在视图控制器类实现中:

// this is a UIView subclass with autolayout
private var headerView = MyHeaderView()


override func loadView() {
super.loadView()
// ...
self.tableView.tableHeaderView = headerView
self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension
// ...
}


override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()


// this is to prevent recursive layout calls
let requiredHeaderHeight = self.tableView.calculatedHeightForHeader()
if self.headerView.frame.height != requiredHeaderHeight {
self.tableView.adjustHeaderHeight()
}
}

关于头 UIView的子视图实现的注释:

  1. 您必须100% 确保页眉视图具有正确的自动布局设置。我建议从只有一个高度约束的简单头视图开始,并尝试上面的设置。

  2. 重写 requiresConstraintBasedLayout并返回 true:

.

class MyHeaderView: UIView {
// ...
override static var requiresConstraintBasedLayout : Bool {
return true
}
// ...
}

我的自动布局非常好:

CGSize headerSize = [headerView systemLayoutSizeFittingSize:CGSizeMake(CGRectGetWidth([UIScreen mainScreen].bounds), 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
headerView.frame = CGRectMake(0, 0, headerSize.width, headerSize.height);
self.tableView.tableHeaderView = headerView;

For Xamarin users:

public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();


TableviewHeader.SetNeedsLayout();
TableviewHeader.LayoutIfNeeded();


var height = TableviewHeader.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize).Height;
var frame = TableviewHeader.Frame;
frame.Height = height;
TableviewHeader.Frame = frame;
}

假设您将表视图的头视图命名为 TableviewHeader

下面是如何做你的 UIViewController

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()


if headerView.frame.size.height == 0 {
headerView.label.preferredMaxLayoutWidth = view.bounds.size.width - 20
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height


headerView.frame.size = CGSize(width: tableView.bounds.size.width, height: height)
}
}

任何基于约束的 UIView都可以是良好的 tableHeaderView

需要先设置一个 tableFooterView,然后对 tableFooterViewtableHeaderView施加额外的拖尾约束。

- (void)viewDidLoad {


........................
// let self.headerView is some constraint-based UIView
self.tableView.tableFooterView = [UIView new];
[self.headerView layoutIfNeeded];
self.tableView.tableHeaderView = self.headerView;


[self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES;
[self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
[self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES;
[self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;

}

One can find all details and code snippets 给你

我想到了一个办法。将自动布局写入的 xib 头视图包装在一个空 uiview 包装器中,并将头视图分配给 tableView 的 tableViewHeader 属性。

    UIView *headerWrapper = [[UIView alloc] init];
AXLHomeDriverHeaderView *headerView = [AXLHomeDriverHeaderView loadViewFromNib];
[headerWrapper addSubview:headerView];
[headerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(headerWrapper);
}];
self.tableView.tableHeaderView = headerView;

Here's what works for UITableViewController in ios 12,

Drap a UIView into the TableView above all the prototype cells for header and below all the prototype cells for footer. Setup your header and footer as needed. Set all the required constraints.

现在使用以下扩展方法

public static class UITableVIewExtensions
{


public static void MakeHeaderAutoDimension(this UITableView tableView)
{
if (tableView.TableHeaderView is UIView headerView) {
var size = headerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
if (headerView.Frame.Size.Height != size.Height) {
var frame = headerView.Frame;
frame.Height = size.Height;
headerView.Frame = frame;
tableView.TableHeaderView = headerView;
tableView.LayoutIfNeeded();
}
}
}


public static void MakeFooterAutoDimension(this UITableView tableView)
{
if (tableView.TableFooterView is UIView footerView) {
var size = footerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
if (footerView.Frame.Size.Height != size.Height) {
var frame = footerView.Frame;
frame.Height = size.Height;
footerView.Frame = frame;
tableView.TableFooterView = footerView;
tableView.LayoutIfNeeded();
}
}
}
}

并在 UITableViewController 子类的 ViewDidLayoutSubviews 中调用它

public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();


TableView.MakeHeaderAutoDimension();
TableView.MakeFooterAutoDimension();
}

更新为 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
}
}
}

我遇到了获得宽度375pt 的问题,对我来说唯一有效的方法是重新布局 tableView 以获得正确的宽度。我也更喜欢自动布局超过设置帧大小。

下面是对我有用的版本:

Xamarin.iOS

public static void AutoLayoutTableHeaderView(this UITableView tableView, UIView header)
{
tableView.TableHeaderView = header;
tableView.SetNeedsLayout();
tableView.LayoutIfNeeded();
header.WidthAnchor.ConstraintEqualTo(tableView.Bounds.Width).Active = true;
tableView.TableHeaderView = header;
}

迅速版本(修改自@Ben Packard 答案)

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.setNeedsLayout()
self.layoutIfNeeded()
header.widthAnchor.widthAnchor.constraint(equalTo: self.bounds.width).isActive = true
self.tableHeaderView = header
}
}

I created a subclass of UITableView and used UIStackView for both header and footer, it also enables setting more than one view.

Https://github.com/omaralbeik/stackabletableview

在大多数情况下,最好的解决方案就是不要反对框架,而是采用自动调整大小的掩护:

// embrace autoresizing masks and let the framework add the constraints for you
headerView.translatesAutoresizingMaskIntoConstraints = true
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]


// figure out what's the best size based on the table view width
let width = self.tableView.frame.width
let targetSize = headerView.systemLayoutSizeFitting(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
headerView.frame.size = targetSize
self.tableView.tableHeaderView = headerView

通过使用自动调整大小掩码,您可以告诉框架,当视图更改其大小时,视图应该如何更改其大小。但是这个更改是基于您设置的初始帧的。

我的解决方案是创建一个这样的新类。

class BaseTableHeaderView: UIView {


func sizeToFitBasedOnConstraints(width: CGFloat = Screen.width) {
let size = systemLayoutSizeFitting(CGSize(width: width, height: 10000),
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel)
frame = CGRect(origin: .zero, size: size)
}


override func willMove(toSuperview newSuperview: UIView?) {
sizeToFitBasedOnConstraints()
super.willMove(toSuperview: newSuperview)
}


}

To use it, simply add all your subviews onto an instance of BaseTableHeaderView and attach it to your table view.

let tableHeaderView = BaseTableHeaderView()
tableHeaderView.addSubview(...)
tableView.tableHeaderView = tableHeaderView

它将根据其约束自动调整大小。

在检查了其他解决方案,过去工作,但不再工作,我创建了一个以下解决方案的头部视图与多行标签,工作很好,我对 iOS 12IOS13IOS14IOS15在所有情况下也与设备旋转。与直接将视图设置为表头视图不同,可以使用包装器视图,该视图在实际视图更改高度时自动更新视图的高度。在项目中添加以下类:

class TableViewHeaderHelperView: UIView {
private weak var headerView: UIView?
private weak var tableView: UITableView?
private var lastUpdatedHeight: CGFloat = 0


init(headerView: UIView) {
self.headerView = headerView
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = true
addSubview(headerView)
}


required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}


func addToTableView(_ tableView: UITableView) {
self.tableView = tableView
tableView.tableHeaderView = self
headerView?.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true
}


func removeFromTableView() {
self.tableView?.tableHeaderView = nil
self.tableView = nil
}


override func layoutSubviews() {
super.layoutSubviews()
refreshHeaderHeightIfNeeded()
}


func refreshHeaderHeightIfNeeded() {
DispatchQueue.main.async {
let headerViewSize = self.headerView?.bounds.size ?? .zero
if headerViewSize.height != self.lastUpdatedHeight {
self.lastUpdatedHeight = headerViewSize.height
self.frame.size = headerViewSize
self.tableView?.tableHeaderView = self
}
}
}
}

然后,您可以使用这个 helper 类,如下所示:

let headerWrapperView = TableViewHeaderHelperView(headerView: yourRealHeaderView)
headerWrapperView.addToTableView(tableView)

另外,我在我的库 LSCategories中也使用了类似的方法,它提供了各种扩展,包括 UITableView 类的扩展,它允许在表头视图中设置一个自动布局视图,或者在表尾视图中设置一行代码,所以你也可以尝试这样做。