UITableView 标头上的自动布局

Posted

技术标签:

【中文标题】UITableView 标头上的自动布局【英文标题】:Auto Layout on UITableView header 【发布时间】:2013-09-17 19:57:05 【问题描述】:

我一直在寻找关于如何将自动布局添加到 UITableView 的明确答案。到目前为止,我的代码如下所示:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section 
    UINib *nib = [UINib nibWithNibName:@"HomeHeaderView" bundle:nil];
    UIView *headerView = (UIView *)[nib instantiateWithOwner:self options:nil][0];
    [headerView.layer setCornerRadius:6.0];
    [headerView setTranslatesAutoresizingMaskIntoConstraints:NO];

//    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(headerView);
//    NSMutableArray *headerConstraints = [[NSMutableArray alloc] init];
//    [headerConstraints addObject:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[headerView]-|" options:0 metrics:nil views:viewsDictionary]];
//        [headerConstraints addObject:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[headerView]-|" options:0 metrics:nil views:viewsDictionary]];
//    [self.actionsTableView addConstraints:headerConstraints];
//    [self.view addSubview:headerView];
    tableView.tableHeaderView = headerView;
    [headerView layoutSubviews];

    NSLayoutConstraint *centerX = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
    NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
    NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:300];
    NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1 constant:90];
    [self.view addConstraints:@[centerX, centerY, width, height]];

    return headerView;

我的标题视图基本上有一个 nib 文件,我想将该 nib 放在 UITableViewHeader 的中心。我希望它在纵向和横向上相应地增长和缩小。老实说,我不确定我是否正确设置了约束。我不确定我的toItem 应该是视图控制器的视图,还是表视图本身。

我也不知道是否应该将 headerview 作为子视图添加到视图控制器的视图或 tableview 本身。

或者,我不确定设置 tableView.tableHeaderView = headerView 是否足够。

我真的不知道这样的最佳实践是什么。我不确定这一切是否也可以在 IB 中完成。目前,使用您看到的代码,我收到此错误:

'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'

正是因为那个错误,我才加了[headerView layoutSubviews]

对此有什么想法?提前致谢!

【问题讨论】:

【参考方案1】:

真正的问题是您将viewForHeaderInSection: 与表格的headerView 混淆了。它们是无关的。

前者是 section 的标题。您从委托方法返回视图。

后者是表格的标题。您设置视图,可能在您的viewDidLoad

约束以正常方式运行。但它们应该只是对其子视图的内部约束。在您形成它时,该视图不在您的界面中。而且它的大小和位置在当时并不取决于你。如果是节标题,它将自动调整大小以正确适应(根据表格的宽度和表格或委托对标题高度的声明)。如果是表头,你可以给它一个绝对高度,但它的宽度会被调整到合适的大小。

这里是一个完整的例子,它构建了一个在其子视图上具有内部约束的节头。

- (UIView *)tableView:(UITableView *)tableView 
        viewForHeaderInSection:(NSInteger)section 
    UITableViewHeaderFooterView* h =
        [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"Header"];
    if (![h.tintColor isEqual: [UIColor redColor]]) 
        h.tintColor = [UIColor redColor];
        h.backgroundView = [UIView new];
        h.backgroundView.backgroundColor = [UIColor blackColor];
        UILabel* lab = [UILabel new];
        lab.tag = 1;
        lab.font = [UIFont fontWithName:@"Georgia-Bold" size:22];
        lab.textColor = [UIColor greenColor];
        lab.backgroundColor = [UIColor clearColor];
        [h.contentView addSubview:lab];
        UIImageView* v = [UIImageView new];
        v.tag = 2;
        v.backgroundColor = [UIColor blackColor];
        v.image = [UIImage imageNamed:@"us_flag_small.gif"];
        [h.contentView addSubview:v];
        lab.translatesAutoresizingMaskIntoConstraints = NO;
        v.translatesAutoresizingMaskIntoConstraints = NO;
        [h.contentView addConstraints:
         [NSLayoutConstraint 
          constraintsWithVisualFormat:@"H:|-5-[lab(25)]-10-[v(40)]"
          options:0 metrics:nil views:@@"v":v, @"lab":lab]];
        [h.contentView addConstraints:
         [NSLayoutConstraint 
          constraintsWithVisualFormat:@"V:|[v]|"
           options:0 metrics:nil views:@@"v":v]];
        [h.contentView addConstraints:
         [NSLayoutConstraint
          constraintsWithVisualFormat:@"V:|[lab]|"
           options:0 metrics:nil views:@@"lab":lab]];
    
    UILabel* lab = (UILabel*)[h.contentView viewWithTag:1];
    lab.text = self.sectionNames[section];
    return h;

【讨论】:

您能否提供代码 sn-ps 以说明如何进行?在这一点上,我仍然对如何对节标题或 tableview 标题进行约束感到困惑:-\。 您以正常方式进行操作。但它们应该只是对其子视图的内部约束。在您形成它时,该视图不在您的界面中。如果它是节标题,它将自动调整大小以正确适应。如果是表头,你可以给它一个绝对高度,但它的宽度会被调整到合适的大小。 不幸的是,它不起作用。 UITableViewHeaderFooterViewcontentView 已计算大小,您将在运行时收到 Unable to simultaneously satisfy constraints 警告。看起来虽然可以使用 AutoLayout 自动计算单元格高度,但对于节标题却不能这样做。刚刚在 ios 8.1 上检查过。 @matt 如果您以一种应该拉伸标题以适应其内容的方式设置约束 - 它在 iOS 8 上不起作用。您将得到 Unable to simultaneously satisfy constraints 就像我说的那样。如果您将自定义子视图直接添加到标题视图而不是 contentView - 它会起作用。 @matt 这与喜欢与否无关 :-) 您很好地解释了如何将带有约束的自定义视图添加到标题视图。我同意这是这样做的正确方法。不幸的是,它会破坏 iOS 8 自动管理的约束。我只是展示了如何解决该问题以实现自动调整标题大小。您可以返回UITableViewAutomaticDimension,而不是在heightForHeaderInSection 中定义固定高度,它会自动调整标题高度。【参考方案2】:

我发现provided by matt 的解决方案可能并不完美,因为他正在向UITableViewHeaderFooterViewcontentView 添加自定义视图和约束。这总是在运行时导致自动布局警告:Unable to simultaneously satisfy constraints 当我们想要动态标题高度时。

我不确定原因,但我们可以假设 iOS 为 contentView 添加了一些额外的约束,用于设置该视图的固定宽度和高度。运行时生成的警告告诉我们手动添加的约束不能满足这些约束,这很明显,因为我们的约束应该拉伸标题视图,以便子视图可以适应它。

解决方案非常简单——不要使用UITableViewHeaderFooterViewcontentView,只需将您的子视图直接添加到UITableViewHeaderFooterView。我可以确认它在 iOS 8.1 上没有任何问题。如果您想添加多个视图并更改标题的背景颜色,请考虑添加填充标题视图的UIView(感谢 AutoLayout 约束),然后添加您希望对该视图具有的所有子视图(我称之为@ 987654330@)。这样我们就可以避免任何 AutoLayout 问题,并在 UITableView 中自动调整标题。

【讨论】:

【参考方案3】:

这是一个巧妙的解决方案:

可选:initWithStyle:UITableViewStyleGrouped 防止浮动tableViewHeader

制作两个属性,标签只是为了演示:

@property (nonatomic, strong, readwrite) UIView *headerView;
@property (nonatomic, strong, readwrite) UILabel *headerLabel;

在 viewDidLoad 中设置所有内容:

self.headerView = [[UIView alloc] initWithFrame:CGRectZero];
self.headerLabel = [[UILabel alloc] init];
self.headerLabel.text = @"Test";
self.headerLabel.numberOfLines = 0; //unlimited
self.headerLabel.textAlignment = NSTextAlignmentCenter;
self.headerLabel.translatesAutoresizingMaskIntoConstraints = NO; //always set this to NO when using AutoLayout
[self.headerView addSubview:self.headerLabel];

NSString *horizontalFormat = @"H:|-[headerLabel]-|";
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:horizontalFormat options:0 metrics:nil views:@@"headerLabel":self.headerLabel];
[self.headerView addConstraints:horizontalConstraints];

NSString *verticalFormat = @"V:|-[headerLabel]-|";
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:verticalFormat options:0 metrics:nil views:@@"headerLabel":self.headerLabel];
[self.headerView addConstraints:verticalConstraints];

在viewForHeaderInSection中:

return self.headerView;

在 heightForHeaderInSection:

self.headerLabel.preferredMaxLayoutWidth = tableView.bounds.size.width;
return [self.headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

【讨论】:

这种方法很棒。谢谢。

以上是关于UITableView 标头上的自动布局的主要内容,如果未能解决你的问题,请参考以下文章

在 UITableView 标头中将标签约束设置为 0 时自动布局失败

UITableView Header 上的自动布局

具有自动布局的 UITableView 中的动态单元格高度

插入时具有自动布局和 NSFetchedResultscontroller 单元布局错误的 UITableview

iOS:在自动布局下更改 UITableView 高度

如何使用自动布局以编程方式创建 UITableView?