自定义UITableViewCell中的多个UILabel

在我创build的这个iOS 8应用程序中,我有一个tableview,我需要它们自我resize。 我使用自动布局实现它,它的工作原理。 几乎。 这是现在的样子。

在这里输入图像说明

单元格内有3个标签。 有lorem ipsum文本的主要标签。 带有数字串的字幕(这是两个单独的标签,因为它们具有相同的颜色可能会令人困惑)。然后是带有小黑色文本的第三个标签。

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

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

在这里输入图像说明

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

在这里输入图像说明

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

我没有得到任何错误,甚至在我的自动布局约束的警告。

在这里输入图像说明

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

 tableView.estimatedRowHeight = 100 tableView.rowHeight = UITableViewAutomaticDimension 

任何人都可以告诉我,我可能在这里做错了吗?

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

我一直摆弄自动布局约束,但我似乎无法得到这个工作。 任何帮助,将不胜感激。

谢谢。


更新:

在本教程的帮助下,我find了一些有用的指针。 据此,每个子视图都应该有一个约束,它的所有方面都应该有自上而下的约束,这有助于自动布局来计算单元的高度。 在我原来的文章,我有每个标签之间的垂直空间,所以我认为这是自动布局无法计算适当的高度的原因。

所以我做了一些改变。

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

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

在这里输入图像说明

但是,如果我将设备旋转到横向并将其重新转换为纵向,则所有单元格都将正确resize以适应两个标签!

在这里输入图像说明

不过还是不明白为什么这一开始不会发生。 更新的Xcode项目在这里 。

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

Xcode 6界面生成器引入了一个新的选项,将此属性设置为自动 。 不幸的是,有一些严重的错误(如Xcode 6.2 / iOS 8.2),当从nib或Storyboard加载单元格时,这些错误没有被正确/自动地设置。

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

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

只是单单添加这个代码不起作用的原因是,当这三行代码在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作为高度。 那是什么 对于这里详细讨论的问题,这是一个简单的解决方法,如果您的约束需要比单元格的contentView的当前高度更多的垂直空间,则会得到一个约束exception,但实际上并不表示任何实际问题。 单元格或其任何子视图的高度在这一点上并不重要,因为我们只关心如何获得每个标签的最终宽度。

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

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

这肯定是UIKit中的一个苹果bug,我们现在必须解决这个问题(所以请给苹果提供bug报告 ,让他们优先考虑修补程序!)。

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

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

我遇到了同样的问题,我发现了一个简单的解决scheme来解决它。

 - (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; } 

自行筛选效果很好。 在我的代码中,我垂直放置了两个标签,它们都是dynamic高度。 单元格的高度正确设置为包含两个标签。

假设你没有像其他人所build议的那样约束你的错误,这个问题似乎源于使用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; } 

对于任何要多行的标签,都要适当调整宽度,然后重新计算适当的高度。 这也适用于dynamic字体大小。

就像smileyborg所提到的,如果你不使用contentView的全部宽度,你可以引用约束条件,并从宽度中减去它们。

编辑:我以前是在单元上运行“layoutIfNeeded”,但这是造成性能问题,似乎并不需要。 删除它并没有给我造成任何问题。

这里有两个问题。

1 /现在,使用Visual Format Language,你的单元格的垂直约束可以这样翻译:

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

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

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

这两组限制条件不能很好地混合在一起,并且会随着第二个问题而显示出模糊性。

2 /出于某些原因, actionsLabel在启动应用程序时被限制为一行。 然后,当您将设备旋转到横向模式时, actionsLabel接受显示两行或更多行。 然后,当您将设备旋转回肖像模式时, actionsLabel会一直显示两行或更多行。 但是,由于actionsLabel并不是您单元的高度限制的一部分,所以它与您单元格的边界重叠。

如果你想解决所有这些问题,我build议先从头开始重build你的xib文件。 这可以治愈你的行为actionsLabel奇怪的行为(只有当你旋转你的设备时,两行或更多)。

然后,你将不得不像这样定义你的约束:

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

当然,您可以为标签定义其他最小高度约束(>=21) 。 以同样的方式,您的底部边距可以设置为另一个值-(10)-

附录

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

在这里输入图像说明

我尝试了“fogisland”的非常简单和优雅的解决scheme – 它没有工作。 幸运的是,我发现另外一条线使它在方向上工作。 只要告诉系统你不仅build议一个新的布局(layoutIfNeeded),你明确地要求它(setNeedLayout)

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