当使用自动布局、IB和字体大小时,表头视图高度错误

36

我正在尝试为我的UITableViewController创建一个标题视图(不是区域标题,我已经有了那些)。我在界面构建器中设置了一个XIB。所有连接都已连接,并且运行得非常好...除了表格没有给它足够的空间!我的问题是表格的顶部与表格头有一点重叠。

我的XIB为所有按钮设置了自动布局,并且IB很高兴约束没有冲突/含糊不清。视图设置为自由形式大小,在我的情况下为320 x 471。然后,在视图的约束条件中,我设置了相同的固有大小。

现在,这对于我的表格来说完美无缺。一切看起来都很棒。但是,如果我手动更改代码中标题视图中的任何字体,则布局会使视图变大,并最终出现在我的表格下面。

有什么办法可以在设置字体和大小后让tableviewcontroller留出足够的空间用于标题视图吗?希望我解释清楚了。


也许这个可以解决问题:https://dev59.com/TmzXa4cB1Zd3GeqPRkT3#16324668 - Ramy Al Zuhouri
如果我在视图控制器代码中更改各种UI元素的字体和大小,标题视图的大小才会出错。屏幕截图可用。表格的标题以大照片结束,然后表格开始。 - jedipixel
我正在调整我的自动布局约束,以防它们是问题所在。它们都没有警告地编译通过了,但也许有些设置我错过了。 - jedipixel
我也在处理这个问题。我的头部视图应该展开到其内容的高度,但实际上并没有展开。你找到解决方案了吗? - Greg W
我发现自动布局在表头或表尾视图的高度调整方面效果不佳。所有其他子视图之间的空间都很好,但是我唯一能够改变表头或表尾视图高度的方法是显式地设置框架。这很不幸,但我还没有找到可行的替代方法。 - idStar
显示剩余2条评论
11个回答

67

注意:Swift 3+版本可在此处找到:https://gist.github.com/marcoarment/1105553afba6b4900c10#gistcomment-1933639


该想法是使用systemLayoutSizeFittingSize:targetSize来计算标题的高度。

  

返回满足其所持有的约束条件的视图的大小。   考虑它所持有的所有约束条件以及其子视图的约束条件,   确定视图的最佳大小。

更改标题高度后,必须重新分配tableHeaderView属性以调整表单元格。

基于此答案: 在UITableView中使用自动布局进行动态单元格布局和可变行高

- (void)sizeHeaderToFit
{
    UIView *header = self.tableView.tableHeaderView;

    [header setNeedsLayout];
    [header layoutIfNeeded];

    CGFloat height = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    CGRect frame = header.frame;

    frame.size.height = height;
    header.frame = frame;

    self.tableView.tableHeaderView = header;
}

2
帮助我的是阅读和重新分配: UIView *header = self.tableView.tableHeaderView;self.tableView.tableHeaderView = header;。 我很好奇为什么。 - gabriel14
2
有没有一种方法可以通过观察自动布局来触发此代码的执行,而不是直接从可能更改自动布局的所有位置调用此函数? - kjam
4
对我来说完全完美地运作 - 我在答案中没有看到的唯一一件事是在哪里调用它。 我在 viewDidLayoutSubviews 中添加了一个调用,对我很有效。 - Josh Brown
4
这对我的iPhone 6 Plus(iOS 9.1)也有效,但在我的iPhone 5s(iOS 8.0)和iPhone 4s(iOS 8.1)上无效。 在viewDidLayoutSubviews中放置此调用会产生非常奇怪的结果,它会递归地调用viewDidLayoutSubviews...而将代码放在其他地方也不起作用,我已经将代码放在了我知道在viewDidLayoutSubviews之后被调用的位置。有任何想法吗? - ClockWise
2
@ClockWise,我发现当您使用UITableViewController时会出现这种行为,因为每次设置tableView header时它都会再次调用布局。我建议切换到UIViewController并在其中嵌入UITableView,这将消除问题。 - Vasily
显示剩余4条评论

15

当我尝试使用接口构建器将表视图标题设置为自定义视图并使用自动布局时,遇到了类似的问题。

这样做时,我会发现它会被一个部分标题遮挡。

我的解决方法是将“虚拟视图”用作表头视图,然后将我的自定义视图添加到该虚拟视图作为子视图。我认为这允许自动布局根据约束和包含视图的框架配置外观。这可能不如vokilam的解决方案优雅,但对我有用。


CGRect headerFrame = CGRectMake(0, 0, yourWidth, yourHeight);
UIView *tempView = [[UIView alloc] initWithFrame:headerFrame];
[tempView setBackgroundColor:[UIColor clearColor]];
YourCustomView *customView = [[YourCustomView alloc] initWithFrame: headerFrame];
[tempView addSubview:movieHeader];
self.tableView.tableHeaderView = tempView;

1
虽然代码不是非常干净,但它对我来说是有效的,速度也很快,让我能够顺利继续进行。 - echappy

9
由于您的tableHeaderView是使用自动布局从xib初始化的,因此您自定义的headerViewsuperView之间的约束未知。您应该添加自定义headerViewconstraints

1.在viewDidLoad中将您的自定义headerView分配给tableView的tableHeaderView

 - (void)viewDidLoad
{
    [super viewDidLoad];
    UIView *yourHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"yourHeaderView" owner:nil options:nil] objectAtIndex:0];
    //important:turn off the translatesAutoresizingMaskIntoConstraints;apple documents for details
     yourHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
    self.tableView.tableHeaderView = yourHeaderView;
}

2. 在 - (void)updateViewConstraints 中,添加自定义 headerView 的基本约束。

- (void)updateViewConstraints
{
    NSDictionary *viewsDictionary = @{@"headerView":yourHeaderView};

    NSArray *constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[headerView(121)]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];

    NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[headerView(320)]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];
    [self.headerView addConstraints:constraint_H];
    [self.headerView addConstraints:constraint_V];

    NSArray *constraint_POS_V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[headerView]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];

    NSArray *constraint_POS_H = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerView]"
                                                                    options:0
                                                                    metrics:nil
                                                                      views:viewsDictionary];
    [self.tableView addConstraints:constraint_POS_V];
    [self.tableView addConstraints:constraint_POS_H];

    [super updateViewConstraints];
}

好的! PS:这里是相关文档:调整视图控制器的视图大小


这对我很有效。如果您在tableView上使用等宽约束条件,它也可以完全适应不同的方向。 - Albert Bori
我认为这是最优雅的解决方案。非常感谢。 - atulkhatri

8
我的问题得到解决的方法是这样的:
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    sizeHeaderToFit()
}

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

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

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

        // Needed or we will stay in viewDidLayoutSubviews() forever
        if height != newFrame.size.height {
            newFrame.size.height = height
            headerView.frame = newFrame

            tableView.tableHeaderView = headerView
        }
    }
}

我在某处找到了这个解决方案并且它非常有效,尽管我忘记了具体在哪里找到的。


我不得不将 let height 更改为一个固定值,但这解决了我的问题。 - Chris

4

我发现vokilam的回答非常有效。这是他的解决方案,使用Swift语言。

func sizeHeaderToFit() {
    guard let header = tableView.tableHeaderView else { return }
    header.setNeedsLayout()
    header.layoutIfNeeded()
    let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    header.frame.size.height = height
    tableView.tableHeaderView = header
}

1
提醒一下,在Swift中,当更改框架值时,您不再需要创建一个占位符变量。 header.frame.height = height 就可以很好地工作。 - Chris C
哦,太酷了,谢谢!我只进行了一个微调 header.frame.size.height = height。感谢你的提醒。 - Mr Rogers

2
其他答案指导了我正确调整tableHeaderView的方向。然而,我在header和tableView单元格之间出现了间隙。这可能会导致您的单元格与标题重叠,具体取决于标题是增大还是缩小。
我从这里开始:
 tableView.reloadData()
 let height = tableViewHeader.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
 tableView.tableHeaderView?.frame.size.height = height

为了解决重叠/间隙问题,我所要做的就是将tableView.reloadData()移动到设置新高度之后,而不是之前。
修改为:
这样:
 let height = tableViewHeader.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
 tableView.tableHeaderView?.frame.size.height = height
 tableView.reloadData()

1

结合@SwiftArchitect、@vokilam和@mayqiyue的答案,在iOS 10.x/Xcode 8.2.1下,使用通过编程方式创建的tableHeaderView而不是nib(虽然我不认为这不能在nib中工作),这里是我所做的:

在您的视图控制器的-viewDidLoad-init方法中创建视图,或者您正在使用表视图的任何地方:

MyCustomTableHeaderView *myCustomTableHeaderView = [[MyCustomTableHeaderView alloc] init]; // Don't set its frame
myCustomTableHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
self.myTableView.tableHeaderView = myCustomTableHeaderView;
self.myCustomTableHeaderView = myCustomTableHeaderView;  // We set and update our constraints in separate methods so we store the table header view in a UIView property to access it later

无论您在哪里设置自定义标题视图的约束(可以在`updateConstraints`中,但该方法可能会被多次调用):

// We use Pure Layout in our project, but the workflow is the same:
// Set width and height constraints on your custom view then pin it to the top and left of the table view
// Could also use VFL or NSLayoutConstraint methods
[self.myCustomTableHeaderView autoSetDimension:ALDimensionWidth toSize:CGRectGetWidth(self.superview.frame)]; // Width set to tableview's width
[self.myCustomTableHeaderView autoSetDimension:ALDimensionHeight toSize:height]; // Whatever you want your height to be
[self.myCustomTableHeaderView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[self.myCustomTableHeaderView autoPinEdgeToSuperviewEdge:ALEdgeLeft];

// This is important
[self.myCustomTableHeaderView layoutIfNeeded]; // We didn't need to call -setNeedsLayout or reassign our custom header view to the table view's tableHeaderView property again, as was noted in other answers

1
在我的情况下,systemLayoutSizeFittingSize返回了不正确的大小,因为其中一个子视图使用了宽高比(4:1)约束。当我将其更改为固定高度约束值(100)后,它开始按预期工作。

0
以下代码对我有用: 将以下代码添加到您的 UIView 类中 -
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];

if (self) {
    self.frame = CGRectMake(0, 0, self.frame.size.width, [[self class] height]);
}

return self;

}


0

@rmigneco的答案对我有用,这是它的Swift版本:

let headerFrame = CGRectMake(0, 0, 100, 1);
let tempView = UIView(frame: headerFrame);
tempView.backgroundColor = UIColor.clearColor()

let yourCustomView = CustomView(frame: headerFrame)
tempView.addSubview(yourCustomView)

self.tableView.tableHeaderView = tempView

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接