自定义UITableViewCell中的多个UILabel

时间:2014-09-20 09:07:09

标签: ios uitableview swift autolayout ios8

在我创建的iOS 8应用程序中,我有一个tableview,我需要它们自我调整大小。我使用自动布局实现它,它的工作原理。几乎。以下是它现在的样子。

enter image description here

单元格内有3个标签。主要标签有lorem ipsum文本。带有数字串的字幕(这是两个单独的标签。可能会因为颜色相同而混淆。)然后第三个标签带有小黑色文字。

第一个标签正确调整大小没有问题,第二个标签相应地上下移动。但问题在于第三个小标签。正如您所看到的,它没有调整自身大小以适应所有文本。

现在发生了一件奇怪的事情。我把它变成了风景,这就是它。

enter image description here

由于存在空间,标签正在显示其应该的整个文本。精细。然后我把它变回肖像。

enter image description here

现在小标签已调整大小以适应其所有文本,但它溢出了单元格边界。我试着让细胞更大,但它没有用。由于这是自我调整细胞,我不认为这是正确的方法。

我没有收到任何错误,甚至没有警告我的自动布局限制。

enter image description here

我在viewDidLoad()方法中设置了这两行代码。

tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension

有谁能告诉我这里可能做错了什么?

由于仅通过查看图片很难回答,而且我没有更多的代码可以在上面的代码段旁边发布,我上传了一个可运行的Xcode项目来演示问题here。 (有两个自定义单元格。基本上它是同一个单元格,只是高度在第二个单元格中增加。)

我一直在摆弄汽车布局限制,但我似乎无法实现这一目标。任何帮助将不胜感激。

谢谢。


更新

tutorial的帮助下,我找到了一些有用的指示。根据它,每个子视图应该具有固定其所有边的约束,并且应该存在从上到下的约束,这有助于自动布局以计算单元的高度。在我原来的帖子中,我在每个标签之间都有垂直空间,所以我认为这是自动布局无法计算出合适高度的原因。

所以我做了一些改变。

  • 我将标签之间的垂直空间缩小为0,并在顶部和中间标签以及中间和底部标签之间设置垂直空间限制。
  • 我将top,top,trailing约束添加到顶部标签。
  • 领先并落后于中间标签。
  • 领先,底部,尾随标签。

现在,这是另一个奇怪的部分。当我第一次运行时,底部标签裁剪问题仍然存在。

enter image description here

但是,如果我将设备旋转到横向并将其转回肖像,则所有单元格都会正确调整大小以适合两个标签!

enter image description here

但仍然无法弄清楚为什么一开始并不会发生这种情况。更新的Xcode项目为here

5 个答案:

答案 0 :(得分:81)

这里的问题是多行标签'preferredMaxLayoutWidth属性。这是告诉标签应该自动换行的属性。必须正确设置才能使每个标签的intrinsicContentSize具有正确的高度,这最终将是用于确定单元格高度的自动布局。

Xcode 6 Interface Builder引入了一个新选项,可将此属性设置为 Automatic 。不幸的是,有一些严重的错误(从Xcode 6.2 / iOS 8.2开始),在从笔尖或故事板加载单元格时没有正确/自动设置。

为了解决这个错误,我们需要将preferredMaxLayoutWidth设置为在表格视图中显示后完全等于标签的最终宽度。实际上,我们希望在从tableView:cellForRowAtIndexPath:返回单元格之前执行以下操作:

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)

仅仅添加此代码不起作用的原因是因为当这3行代码在tableView:cellForRowAtIndexPath:中执行时,我们使用每个标签的宽度来设置preferredMaxLayoutWidth - 但是,如果你在这个时间点检查标签的宽度,标签宽度与显示单元格及其子视图布局后最终的标签宽度完全不同。

我们如何让标签宽度在这一点上准确,以便它们反映出它们的最终宽度?这是使它们聚集在一起的代码:

// Inside of tableView:cellForRowAtIndexPath:, after dequeueing the cell

cell.bounds = CGRect(x: 0, y: 0, width: CGRectGetWidth(tableView.bounds), height: 99999)
cell.contentView.bounds = cell.bounds
cell.layoutIfNeeded()

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)
好的,那我们在这做什么呢?好吧,您会注意到添加了3行新代码。首先,我们需要设置此表格视图单元格的宽度,使其与表格视图的实际宽度相匹配(这假定表格视图已经布局并具有其最终宽度,应该是这种情况)。我们实际上只是尽早使单元格宽度正确,因为表视图最终会这样做。

您还会注意到我们正在使用99999作为身高。那是什么意思?对于详细讨论here中讨论的问题,这是一个简单的解决方法,如果您的约束需要比单元格的contentView的当前高度更多的垂直空间,那么您将获得一个实际上并未表示任何实际问题的约束异常。单元格或其任何子视图的高度此时并不重要,因为我们只关心获取每个标签的最终宽度。

接下来,我们通过将contentView的边界设置为等于单元格的边界,确保单元格的contentView与我们刚刚分配给单元格本身的大小相同。这是必要的,因为您创建的所有自动布局约束都与contentView相关,因此contentView必须是正确的大小才能正确解决。只需手动设置单元格的大小,会自动调整contentView的大小以匹配。

最后,我们强制在单元格上进行布局传递,这将使自动布局引擎解决您的约束并更新所有子视图的帧。自从细胞和细胞contentView现在具有与表视图中运行时相同的宽度,标签宽度也将是正确的,这意味着设置到每个标签的preferredMaxLayoutWidth将是准确的并且将导致标签在正确的时间换行,这当然意味着当在表格视图中使用单元格时,标签的高度将被正确设置!

这绝对是UIKit中的Apple漏洞,我们现在必须解决这个问题(所以请与Apple一起file bug reports,以便他们优先考虑修复!)。

最后要注意的是:如果表格视图单元格的contentView宽度未扩展表格视图的整个宽度,则此解决方法将遇到麻烦,例如,当右侧显示区段索引时。在这种情况下,您需要确保在设置单元格的宽度时手动考虑这一点 - 您可能需要对这些值进行硬编码,例如:

let cellWidth = CGRectGetWidth(tableView.bounds) - kTableViewSectionIndexWidth
cell.bounds = CGRect(x: 0, y: 0, width: cellWidth, height: 99999)

答案 1 :(得分:49)

我遇到了和你一样的问题,我找到了解决问题的简单方法。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     // dequeue cell...

     // do autolayout staffs...or if the autolayout rule has been set in xib, do nothing

     [cell layoutIfNeeded];

     return cell;
}

自我调整很好。在我的代码中,我在垂直方向放置了两个标签,它们都是动态高度。正确设置单元格的高度以包含两个标签。

答案 2 :(得分:6)

假设你的约束没有任何其他人提出的错误,这个问题似乎源于使用UILabel,它允许多行与UITableViewCellAccessory结合使用。当iOS布置单元格并确定高度时,它不会考虑由于此附件而发生的宽度偏移变化,并且您会在不期望的位置进行截断。

假设您希望UILabel扩展内容视图的整个宽度,我编写了一个修复所有字体大小的方法

-(void)fixWidth:(UILabel *)label forCell:(UITableViewCell *)cell {
    float offset = 0;
    switch ([cell accessoryType]) {
        case UITableViewCellAccessoryCheckmark:
            offset = 39.0;
            break;
        case UITableViewCellAccessoryDetailButton:
            offset = 47.0;
            break;
        case UITableViewCellAccessoryDetailDisclosureButton:
            offset = 67.0;
            break;
        case UITableViewCellAccessoryDisclosureIndicator:
            offset = 33.0;
            break;
        case UITableViewCellAccessoryNone:
            offset = 0;
            break;
    }
    [label setPreferredMaxLayoutWidth:CGRectGetWidth([[self tableView]frame]) - offset - 8];
}

只需将其放入您的cellForRowAtIndexPath

即可
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    #Setup the cell
    ...
    // Fix layout with accessory view
    [self fixWidth:[cell label] forCell:cell];
    return cell;
}

对于任何具有多行的标签来正确调整宽度,然后重新计算适当的高度。这也适用于动态字体大小。

就像smileyborg提到的那样,如果你没有使用contentView的全宽,你可以引用约束并从宽度中减去它们。

编辑:我之前在单元格上运行'layoutIfNeeded',但这会产生性能问题,但无论如何似乎都不需要。删除它并没有给我带来任何问题。

答案 3 :(得分:4)

这里有两个问题。

1 /现在,使用可视化格式语言,您的单元格的垂直约束可以这样翻译:

Cell: "V:|-(10)-[nameLabel]-(67)-|"

然后,您设置第二组约束:

Cell: "V:|-(10)-[nameLabel]-(8)-[pnrLabel]-(2)-[actionsLabel]"

这两组约束不能很好地混合,并且会在第二个问题上显示出它们的模糊性。

2 /由于某些原因,actionsLabel在您启动应用时仅限于一行。然后,当您将设备旋转到横向模式时,actionsLabel接受显示两行或更多行。然后,当您将设备旋转回纵向模式时,actionsLabel会一直显示两行或更多行。但是,因为actionsLabel实际上不是细胞高度限制的一部分,它会与细胞的边界重叠。

如果您想解决所有这些问题,我建议您首先从头开始重建xib文件。这样可以解决actionsLabel奇怪的行为(仅在旋转设备时两行或更多行)。

然后,你必须像这样定义你的约束:

Cell: "V:|-(10)-[nameLabel(>=21)]-(8)-[pnrLabel(>=21)]-(2)-[actionsLabel(>=21)]-(10)-|"

当然,您可以为标签定义其他最小高度限制,而不是(>=21)。同样,您的下边距可以设置为-(10)-以外的其他值。

<强>附录

为了回答你的问题,我在.xib文件中创建了一个带有先前约束模式的简单项目。以下图片可以帮助您构建自己的约束。

enter image description here

答案 4 :(得分:1)

我尝试了非常简单优雅的解决方案&#34; fogisland&#34; - 它不起作用。幸运的是,我发现另外一条线使它在alle方向上起作用。告诉系统你不仅建议一个新的布局(layoutIfNeeded),你明确要求它(setNeedLayout)

    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    return cell