在计算UITableViewCell高度(所有样式)时,Autolayout忽略多行detailTextLabel

tir*_*tea 11 uitableview ios autolayout

所以我试图使用内置的UITableViewCell样式 - 特别是UITableViewCellStyleSubtitle - 与(单)行textLabel多行 detailTextLabel.但是(自动)计算的单元格高度始终太短,并且似乎忽略了超过1行细节.

我已经尝试过使用numberOfLines = 0,estimatedRowHeight,UITableViewAutomaticDimension,preferredMaxWidthLayout等,但在所有排列行为中 - 实际上对于所有 UITableViewCell样式 - 看来UITableViewAutomaticDimension单元格高度计算将正确地考虑多行textLabel(耶! ),但错误地假设detailTextlabel最多是单行(nay!).因此,具有多线detailTextLabel的细胞太短,因此细胞内容溢出细胞的顶部和底部.

在此输入图像描述

我发布了一个快速测试应用程序,在这里显示GitHub上的这种行为.添加额外的文本行很好 - 所有单元格样式的高度适当增加以适应 - 但添加额外的细节行不会改变单元格高度,并且很快会导致内容溢出; 文本+细节本身正确布局,并且一起正确地居中在单元格的中间(因此在这种意义上,layoutSubviews正常工作),但整体单元格高度本身不变.

看起来cell.contentView和标签之间几乎没有实际的顶部和底部约束,而是直接从(可能是多行)textLabel和(只有单行)detailTextLabel的高度计算单元格高度.然后一切都集中在单元格的中间...再次,多行textLabel很好,我在textLabel和detailTextLabel之间没有任何不同,但只有前者(正确)调整单元格高度.

所以我的问题是,如果可以使用内置的UITableViewCell样式来可靠地显示多行 detailTextLabels,或者它根本不可能而你需要创建一个自定义子类呢?[或者,几乎等效地,不必覆盖子类中的layoutSubviews并手动重新连接所有约束].


[2016年5月4日]结论:从iOS9开始,多行detailTextLabels无法按预期使用UITableViewAutomaticDimension; 单元格将始终太短,文本/细节将溢出顶部和底部.您必须自己手动计算正确的单元格高度,或者创建和布局您自己的等效自定义UITableViewCell子类,或者(请参阅下面的答案)子类UITableViewCell并修复systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:以返回正确的高度[推荐]

tir*_*tea 20

进一步的研究(参见UITableViewCellTest)表明,当启用UITableViewAutomaticDimension时,系统调用-systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:计算单元格高度,并且这几乎忽略了计算中detailTextLabel的高度(bug!?).因此,对于UITableViewCellStyleSubtitle,单元格高度总是太短[单行detailTextLabel可能不会完全溢出单元格,但这只是因为现有的顶部和底部边距],而对于UITableViewCellStyleValue1UITableViewCellStyleValue2的高度只要detailTextLabeltextLabel更高(例如更多行),它就会太短.这对于没有detailTextLabel的UITableViewCellStyleDefault来说都是一个有争议的问题.

我的解决方案是子类化并修复:

- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize
        withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority
              verticalFittingPriority:(UILayoutPriority)verticalFittingPriority
{
    // Bug finally fixed in iOS 11
    if ([UIDevice.currentDevice.systemVersion compare:@"11" options:NSNumericSearch] != NSOrderedAscending) {
        return [super systemLayoutSizeFittingSize:targetSize
                    withHorizontalFittingPriority:horizontalFittingPriority
                          verticalFittingPriority:verticalFittingPriority];
    }

    [self layoutIfNeeded];
    CGSize size = [super systemLayoutSizeFittingSize:targetSize
                       withHorizontalFittingPriority:horizontalFittingPriority
                             verticalFittingPriority:verticalFittingPriority];
    CGFloat detailHeight = CGRectGetHeight(self.detailTextLabel.frame);
    if (detailHeight) { // if no detailTextLabel (eg style = Default) then no adjustment necessary
        // Determine UITableViewCellStyle by looking at textLabel vs detailTextLabel layout
        if (CGRectGetMinX(self.detailTextLabel.frame) > CGRectGetMinX(self.textLabel.frame)) { // style = Value1 or Value2
            CGFloat textHeight = CGRectGetHeight(self.textLabel.frame);
            // If detailTextLabel taller than textLabel then add difference to cell height
            if (detailHeight > textHeight) size.height += detailHeight - textHeight;
        } else { // style = Subtitle, so always add subtitle height
            size.height += detailHeight;
        }
    }
    return size;
}
Run Code Online (Sandbox Code Playgroud)

并在视图控制器中:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.tableView.estimatedRowHeight = 44.0;
    self.tableView.rowHeight = UITableViewAutomaticDimension;
}
Run Code Online (Sandbox Code Playgroud)

您可以从此处提取完整的子类:MultilineTableViewCell

到目前为止,这个修复似乎运行良好,并让我成功地使用内置的UITableViewCellStyles与多行文本和细节,在具有动态类型支持的自定义单元格中.这避免了手动计算所需单元高度tableView:heightForRowAtIndexPath:或必须创建自定义单元布局的麻烦(和混乱).

[(部分)在iOS11中固定]

Apple 最终修复了iOS11中的这个错误(但仅适用于UITableViewCellStyleSubtitle).我已经更新了我的解决方案,只对11之前的设备进行了必要的修正(否则你最终会在你的单元的顶部和底部留出额外的空间!).


Hlu*_*ung 5

@tiritea 在 Swift 3 中的回答(再次感谢!:D)

// When UITableViewAutomaticDimension is enabled the system calls 
// -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: to calculate the cell height.
// Unfortunately, it ignores the height of the detailTextLabel in its computation (bug !?). 
// As a result, for UITableViewCellStyleSubtitle the cell height is always going to be too short.
// So we override to include detailTextLabel height.
// Credit: http://stackoverflow.com/a/37016869/467588
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
  self.layoutIfNeeded()
  var size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
  if let textLabel = self.textLabel, let detailTextLabel = self.detailTextLabel {
    let detailHeight = detailTextLabel.frame.size.height
    if detailTextLabel.frame.origin.x > textLabel.frame.origin.x { // style = Value1 or Value2
      let textHeight = textLabel.frame.size.height
      if (detailHeight > textHeight) {
        size.height += detailHeight - textHeight
      }
    } else { // style = Subtitle, so always add subtitle height
      size.height += detailHeight
    }
  }
  return size
}
Run Code Online (Sandbox Code Playgroud)