【问题标题】:Multiple UILabels inside a self sizing UITableViewCell自调整大小的 UITableViewCell 中的多个 UILabel
【发布时间】:2014-11-14 19:54:12
【问题描述】:

在我正在创建的这个 iOS 8 应用程序中,我有一个表格视图,我需要它们自行调整大小。我使用自动布局实现了它并且它工作。几乎。这是它现在的样子。

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

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

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

由于有空间,标签显示了它应该显示的整个文本。美好的。然后我把它转回纵向。

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

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

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

tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension

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

由于仅通过查看图像很难回答,并且除了上述 sn-p 之外我没有更多代码可以发布,因此我上传了一个可运行的 Xcode 项目来演示问题here。 (有2个自定义单元格。基本上它是相同的单元格,只是第二个增加了高度。)

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

谢谢。


更新:

tutorial 的帮助下,我找到了一些有用的建议。根据它,每个子视图都应该有约束其所有侧面的约束,并且应该有从上到下的约束,这有助于自动布局计算单元格的高度。在我原来的帖子中,每个标签之间都有垂直空间,所以我认为这就是自动布局无法计算正确高度的原因。

所以我做了一些改变。

  • 我将标签之间的垂直空间减少到 0,并设置了顶部和中间标签以及中间和底部标签之间的垂直空间限制。
  • 我在顶部标签中添加了前导、顶部和尾部约束。
  • 前导和尾随到中间标签。
  • 前导、底部、尾随到底部标签。

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

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

仍然无法弄清楚为什么一开始没有发生这种情况。更新后的 Xcode 项目是here

【问题讨论】:

  • 表格发现在视图出现之前估计标签中文本的确切高度太困难了,但它的表现都很好。我发现如果你将 tableView.estimatedRowHeight = 100 移动到 viewDidAppear 那么这解决了你看到的问题
  • ... 或者更确切地说,表格似乎使用估计的高度作为借口,在首次加载表格时不进行精确计算。但是,如果按照描述的方式移动了估计行高度,则可以解决您所看到的问题。完全删除它会在旋转时产生重绘问题。
  • @GoodbyeStackOverflow 我按照你的建议做了,但我遇到了一些问题。 This is how it looks when you run itwhen you scroll down。我也将同样的方法应用于我的第二个示例和this is the result。前 3 个单元格看起来不错,但请注意,在最后一个单元格中它搞砸了。
  • @Isuru XCode 项目不存在。你能再上传一次吗?我也在做同样的事情。
  • @Hiren 抱歉,我没有那个项目了。这只是一个临时项目。

标签: ios uitableview swift autolayout ios8


【解决方案1】:

我尝试了“fogisland”这个非常简单和优雅的解决方案 - 但它不起作用。幸运的是,我发现另外一条线使它可以在各个方向工作。只需告诉系统您不仅建议了新布局 (layoutIfNeeded),还明确提出了要求 (setNeedLayout)

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

【讨论】:

    【解决方案2】:

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

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

    为了解决这个错误,我们需要将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 的大小以匹配。

    最后,我们强制对单元格进行布局传递,这将使自动布局引擎解决您的约束并更新所有子视图的框架。由于单元格和内容视图现在在表格视图中具有相同的宽度,因此标签宽度也将是正确的,这意味着为每个标签设置的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)
    

    【讨论】:

    • @Isuru 在单元格上强制布局传递后得到的不可满足的约束异常可能不是真正的问题,并且可以轻松解决。有关该问题的完整讨论,请参阅 this thread
    • 谢谢。我花了 2 天时间尝试一切我能找到的东西来让我的标签正确调整大小。奇怪的是,有些确实调整了大小,但其他单元格设计没有。我找不到约束或其他设置的差异。但是,您的解决方案效果很好。我确实创建了一个 UILabel 子类,并将 preferredMaxLayoutWidth 设置放在 setBounds 中,以避免在表视图控制器中硬引用 UILabel。
    • 很棒的帖子。谢谢你。它真的帮助了我,现在我的标签表现得像预期的那样
    • 谢谢!正是我需要的!
    • 强制preferredMaxLayoutWidth 不幸的是会产生不良影响,例如破坏方向更改,并且需要更多代码。 0 行代码 替代方法是使用 UITextView 而不是多行 UILabel
    【解决方案3】:

    假设您的约束没有任何错误,正如其他人所建议的那样,这个问题似乎源于使用允许多行与 UITableViewCellAccessory 结合的 UILabel。当 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”,但这会产生性能问题,而且似乎不需要。删除它对我没有任何问题。

    【讨论】:

      【解决方案4】:

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

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

      而且自定尺寸效果很好。在我的代码中,我垂直放置了两个标签,它们都是动态高度。单元格的高度正确设置为包含两个标签。

      【讨论】:

      • 这是最好的答案。设置 preferredMaxLayoutWidth 的公认答案在我的经验中仍然存在问题。
      • 嘘,我到处寻找答案。所有这些复杂、不雅的解决方案。在这里,一行代码。完成...
      • 不幸的是,我发现这对我不起作用,而 Smileyborg 的解决方案却可以。
      • 从 iOS8.3 开始,这解决了当多行标签固定在其他两个项目之间时的布局问题。
      • 它适用于我 8.1 在上面的函数中添加这一行 cell.layoutIfNeeded() 。休息已经在工作了
      【解决方案5】:

      这里有两个问题。

      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 文件中创建了一个使用先前约束模式的简单项目。下图可以帮助您构建自己的约束。

      【讨论】:

      • 很好,简洁的答案。
      • 感谢您的详细解答。我设法让它工作,但有一个小问题。由于这些标签的宽度是固定的,因此当您转动屏幕横向时,它们不会调整以填充额外的空间。有没有办法对付这种情况?在这些标签上向 Superview 添加尾随空格似乎不起作用。
      • 我已经为我的测试项目中的每个标签设置了宽度限制以进行测试。在您的应用中,不要设置宽度约束,而是将 Trailing Space 设置为每个标签的 Superview 边距约束。不要同时为每个标签设置宽度约束和尾随空间到 Superview 边距约束:您必须在其中一个或另一个之间做出选择。
      • @POB 我试过了,但是去掉了宽度限制让它看起来像thisHere 是我当前的自动布局视图。
      • 不,你是对的。当使用尾随空间来查看边距约束而不是宽度约束时,它不起作用......我试图找出原因。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-27
      • 1970-01-01
      相关资源
      最近更新 更多