如何使用自动布局设置tableHeaderView(UITableView)的高度?


86

最近3或4个小时,我一直在用头撞墙,但似乎无法解决。我有一个带有全屏UITableView的UIViewController(屏幕上还有其他内容,这就是为什么我不能使用UITableViewController的原因),并且我想让tableHeaderView可以使用自动布局来调整大小。不用说,这是不合作的。

请参见下面的屏幕截图。

在此处输入图片说明

因为overviewLabel(例如“在此处列出概览信息。”文本)具有动态内容,所以我使用自动布局来调整其大小,并且它是superview。除了tableHeaderView,它一切都很好地调整了大小,它位于hiearchy中的Paralax Table View的正下方。

我发现调整标题视图大小的唯一方法是通过编程,使用以下代码:

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

在这种情况下,headerFrameHeight是tableViewHeader高度的手动计算,如下所示(innerHeaderView是白色区域,或者第二个“ View”,headerView是tableHeaderView)

CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y;
CGRect overviewSize = [self.overviewLabel.text
                       boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
                       options:NSStringDrawingUsesLineFragmentOrigin
                       attributes:@{NSFontAttributeName: self.overviewLabel.font}
                       context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there's no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);

手动计算是可行的,但是如果将来发生变化,它会很笨拙并且容易出错。我要能够做的就是让tableHeaderView根据提供的约束自动调整大小,就像在其他任何地方一样。但是对于我的一生,我无法弄清楚。

SO上有几篇关于此的文章,但都不清楚,最终使我更加困惑。这里有一些:

将translatesAutoresizingMaskIntoConstraints属性更改为NO并没有任何意义,因为这只会给我带来错误,并且从概念上讲也没有任何意义。

任何帮助将不胜感激!

编辑1: 感谢TomSwift的建议,我得以弄清楚。无需手动计算概览的高度,而是可以按照以下方法为我计算概览,然后像以前一样重新设置tableHeaderView。

[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView.

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

编辑2:正如其他人指出的那样,编辑1中发布的解决方案似乎在viewDidLoad中不起作用。但是,它似乎可以在viewWillLayoutSubviews中工作。下面的示例代码:

// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [_headerWrapper setNeedsLayout];
    [_headerWrapper layoutIfNeeded];
    CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = _headerWrapper.frame;
    headerFrame.size.height = height;
    _headerWrapper.frame = headerFrame;
    _tableView.tableHeaderView = _headerWrapper;
}

2
setTableHeaderView在Xcode6上不起作用。问题是表格头视图重叠了单元格。但是,它适用于Xcode5
DawnSong 2015年

@DawnSong知道Xcode6的解决方案,以便我可以更新答案吗?
Andrew Cross

1
经过大量试用后,我使用UITableViewCell而不是tableHeaderView,它可以工作。
DawnSong 2015年

我也砸了头!
Akshit Zaveri

真正完整的自动布局解决方案在这里
malex '16

Answers:


35

您需要使用 UIView systemLayoutSizeFittingSize:方法来获取标题视图的最小边界大小。

我将在本问答中提供有关使用此API的进一步讨论:

如何调整超级视图的大小以适合具有自动布局的所有子视图?


它以高度= 0返回,尽管我不是100%肯定我已经正确配置了它,因为它不是UITableViewCell,所以没有contentView可以调用“ systemLayoutSizeFittingSize”。我试过的:[self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat高度= [self.headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height =高度; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView:self.headerView]; 我还确认preferredMax ...是有效的。
2014年

啊哈!弄清楚了,我需要执行[self.innerHeaderView systemLayoutSizeFittingSize ...]而不是self.headerView,因为这是实际的动态内容。万分感谢!
Andrew Cross

它可以工作,但是在我的应用程序中高度差异很小。可能是因为我的标签使用的是属性文字和动态类型。
bio

23

我真的与这个争斗不休,并且将设置插入viewDidLoad对我来说不起作用,因为未在viewDidLoad中设置框架,我还遇到了很多混乱的警告,其中标头的封装自动布局高度降低到0我只在iPad演示文稿中显示tableView时才注意到该问题。

对我来说解决问题的是在viewWillLayoutSubviews中而不是viewDidLoad中设置tableViewHeader。

func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        if tableView.tableViewHeaderView == nil {
            let header: MyHeaderView = MyHeaderView.createHeaderView()
            header.setNeedsUpdateConstraints()
            header.updateConstraintsIfNeeded()
            header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
            var newFrame = header.frame
            header.setNeedsLayout()
            header.layoutIfNeeded()
            let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
            newFrame.size.height = newSize.height
            header.frame = newFrame
            self.tableView.tableHeaderView = header
        }
    }

对于使用最少代码的更新解决方案,请尝试以下操作:stackoverflow.com/a/63594053/3933302
Sourabh Sharma

23

我发现了一种使用自动布局来调整表格标题大小(带有或不带有动画)的优雅方法。

只需将其添加到您的View Controller。

func sizeHeaderToFit(tableView: UITableView) {
    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
    }
}

要根据动态变化的标签调整大小:

@IBAction func addMoreText(sender: AnyObject) {
    self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}

override func viewDidLayoutSubviews() {
    // viewDidLayoutSubviews is called when labels change.
    super.viewDidLayoutSubviews()
    sizeHeaderToFit(tableView)
}

要根据约束的变化为调整大小设置动画:

@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!

@IBAction func makeThisTaller(sender: AnyObject) {

    UIView.animateWithDuration(0.3) {
        self.tableView.beginUpdates()
        self.makeThisTallerHeight.constant += 20
        self.sizeHeaderToFit(self.tableView)
        self.tableView.endUpdates()
    }
}

请参阅AutoResizingHeader项目,以查看实际效果。 https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader


嘿p-sun。感谢您的好榜样。似乎我对tableViewHeader有问题。我试图像您的示例一样具有相同的约束,但是没有起作用。我应该如何精确地向要增加高度的标签添加约束?感谢您的任何建议
-Bonnke

1
确保钩住您想要更高@IBOutlet makeThisTaller@IBAction fun makeThisTaller与示例类似的标签。另外,将标签的所有侧面约束到tableViewHeader(例如,顶部,底部,左侧和右侧)。
p-sun

非常感谢!最终通过添加以下行来解决:lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.width其中label是我要增加尺寸的那个。谢谢 !
Bonnke

谢谢!如果您是在View而不是ViewController中执行此操作,则可以覆盖layoutSubviews而不是覆盖viewDidLayoutSubviews
Andy Weinstein

4

此解决方案调整了tableHeaderView的大小,并避免了viewDidLayoutSubviews()我在此处遇到的其他方法中的无限循环:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var headerFrame = headerView.frame

        // comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}

另请参阅此帖子:https : //stackoverflow.com/a/34689293/1245231


1
我也在使用这种方法。认为这是最好的,因为没有摆弄约束。它也非常紧凑。
bio

3

如果标题视图只是在每个视图外观上更新一次,则使用systemLayoutSizeFittingSize:的解决方案有效。就我而言,标题视图已多次更新以反映状态变化。但是systemLayoutSizeFittingSize:始终报告相同的大小。即,对应于第一次更新的大小。

要获得systemLayoutSizeFittingSize:在每次更新后返回正确的大小,我必须先删除表头视图,然后再更新并重新添加它:

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];

2

这在ios10和Xcode 8上对我有用

func layoutTableHeaderView() {

    guard let headerView = tableView.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false

    let headerWidth = headerView.bounds.size.width;
    let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])

    headerView.addConstraints(temporaryWidthConstraints)

    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()

    let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
    let height = headerSize.height
    var frame = headerView.frame

    frame.size.height = height
    headerView.frame = frame

    self.tableView.tableHeaderView = headerView

    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true

}

2

它适用于页眉视图和页脚,只需将页眉替换为页脚

func sizeHeaderToFit() {
    if let headerView = tableView.tableHeaderView {

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame

        tableView.tableHeaderView = headerView
    }
}

1

对于iOS 12及更高版本,以下步骤将确保自动布局在表格和标题中均能正常工作。

  1. 首先创建tableView,然后创建标题。
  2. 在标头创建代码的末尾,调用:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. 更改方向后,再次将标题标记为布局,然后重新加载表格,这需要在报告方向更改后稍稍发生:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{

  [tableV.tableHeaderView setNeedsLayout];
  [tableV.tableHeaderView layoutIfNeeded];
  [tableV reloadData];});

0

就我而言,viewDidLayoutSubviews效果更好。viewWillLayoutSubviews使a的白线tableView出现。我还添加了检查headerView对象是否已经存在的功能。

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if ( ! self.userHeaderView ) {
        // Setup HeaderView
        self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
        [self.userHeaderView setNeedsLayout];
        [self.userHeaderView layoutIfNeeded];
        CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.userHeaderView.frame;
        headerFrame.size.height = height;
        self.userHeaderView.frame = headerFrame;
        self.tableView.tableHeaderView = self.userHeaderView;

        // Update HeaderView with data
        [self.userHeaderView updateWithProfileData];
    }
}

0

很有可能使用UIView带有任何AL内部子视图结构的通用基于AutoLayout的形式tableHeaderView

唯一需要做的就是先设置一个简单的tableFooterView

Letself.headerView是一些基于约束的UIView

- (void)viewDidLoad {

    ........................

    self.tableView.tableFooterView = [UIView new];

    [self.headerView layoutIfNeeded]; // to set initial size

    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;

    // and the key constraint
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}

如果self.headerView在UI旋转下更改高度,则必须实施

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // needed to resize header height
        self.tableView.tableHeaderView = self.headerView;
    } completion: NULL];
}

为此,可以使用ObjC类别

@interface UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView;
@end

@implementation UITableView (AMHeaderView)

- (void)am_insertHeaderView:(UIView *)headerView {

    NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");

    [headerView layoutIfNeeded];

    self.tableHeaderView = headerView;

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

    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
}
@end

还有Swift扩展

extension UITableView {

    func am_insertHeaderView2(_ headerView: UIView) {

        assert(tableFooterView != nil, "Need to define tableFooterView first!")

        headerView.layoutIfNeeded()

        tableHeaderView = headerView

        leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true

        tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
    }
}


0

从此帖子复制

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    if let headerView = tableView.tableHeaderView {

        let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        var headerFrame = headerView.frame
        
        //Comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.