提问



我的问题基本上归结为支持UILabel动态高度的最佳方式(我想其他元素)在UITableCell 中,并且正确调整标签宽度/高度和单元格高度旋转时


我知道如何获得UILabels的预期高度,并相应地调整高度,但是当你支持横向时,事情似乎也变得非常棘手。


我认为这可能是layoutSubviews的路线,但我不确定你如何将它与需要计算单元格高度的表结合起来。另一个有趣的帖子在init上手动设置框架,以确保它们在已知数量的情况下进行计算,但这只能解决部分问题。


所以这里是我正在处理的图像,红色箭头指向动态高度标签,蓝色箭头指向将改变高度的单元格。





我已经设法让它正常工作,但不确定它是否是正确的方法。


以下是我学到的一些内容:




  • cellForRowAtIndexPath中的cell.frame始终以纵向模式显示其大小。 (即,对于iPhone应用程序,它总是报告320)。

  • 在属性中存储原型单元格以供参考具有相同的问题,始终处于纵向模式

  • 自动调整标签宽度的掩码似乎在这个用例中没用,实际上会导致计算旋转高度的问题

  • cellForRowAtIndexPath中的tableView.frame以正确的方向显示其大小

  • 您需要在轮换时调用[tableView reloadData]。我找不到任何其他方式来更新单元格和标签高度



以下是我为此工作所采取的步骤:




  1. 禁用UILabel上的任何自动调整大小的掩码,因为它们会因某种原因干扰获取正确的标签高度

  2. viewDidLoad上我抓取每个标签字体的引用,并且标签宽度百分比的浮点数与纵向中的tableView相比


    CWProductDetailDescriptionCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
    self.descriptionLabelWidthPercentage = cell.descriptionLabel.frame.size.width / 320;
    self.descriptionLabelFont = cell.descriptionLabel.font;
    

  3. heightForRowAtIndexPath中,我使用tableView宽度和我已抓取的百分比计算单元格高度:


    case TableSectionDescription:
        CGFloat labelWidth = self.tableView.frame.size.width * self.descriptionLabelWidthPercentage;
        CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(labelWidth, MAXFLOAT) AndFont:self.descriptionLabelFont];
        return newLabelFrameSize.height + kTextCellPadding;
    

  4. cellForRowAtIndexPath中,我计算标签帧的帧并更新它


    cell = [tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
    ((CWProductDetailDescriptionCell *)cell).descriptionLabel.text = self.product.descriptionText;
    CGRect oldFrame = ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame;
    CGFloat labelWidth = self.tableView.frame.size.width * self.descriptionLabelWidthPercentage;
    CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(labelWidth, MAXFLOAT) AndFont:((CWProductDetailDescriptionCell *)cell).descriptionLabel.font];
    ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, labelWidth, newLabelFrameSize.height);
    

  5. didRotateFromInterfaceOrientation中你需要[self.tableView reloadData]



此方法存在问题:




  1. 您无法利用您在IB中设置的自动调整遮罩和边距


    • 在您cellForRowAtIndexPath (不确定这是否完全正确)之后返回细胞之前,似乎不会开火的

    • 他们以某种方式弄乱了高度计算,你的标签最终比景观要高。


  2. 如果您的标签宽度在纵向和横向上不是相同的百分比,那么您将需要知道这两个百分比

  3. 您需要知道/计算标签宽度百分比。理想情况下,您可以在自动调整大小的蒙版完成后立即计算这些内容。

  4. 只是感觉笨拙。我有一种感觉,我会在编辑模式下遇到更多令人头疼的问题。



所以。这样做的正确方法是什么?我已经看过很多SO问题,但没有一个问题完全解决这个问题。 [52] [53] [54] [55] [56]

最佳参考


显然,它写出了我的巨大问题,并从计算机中休息了解。


所以这是解决方案:



动态计算UILabel高度的主要步骤,并利用自动调整掩码:




  1. 为每个需要调整高度的单元格保留原型单元格

  2. heightForRowAtIndexPath

    • 根据您所在的方向调整参考单元格宽度

    • 然后你可以可靠地使用包含的UILabel框架宽度来计算高度


  3. 除初始设置外,不要在cellForRowAtIndexPath中进行任何帧调整

    • UITableViewCells在调用之后立即执行所有布局工作,所以你的努力充其量是徒劳的,最糟糕的是他们搞砸了


  4. willDisplayCell forRowAtIndexPath中进行帧调整

    • 我很生气我忘了这个

    • 它出现在所有UITableViewCell内部布局之后,就在它绘制之前

    • 您的带有自动调整遮罩的标签已经具有可用于计算的正确宽度




在viewDidLoad中抓取对需要高度调整的单元格的引用



请注意,这些是保留/强(ARC),并且从未添加到表本身


self.descriptionReferenceCell = [self.tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
self.attributeReferenceCell = [self.tableView dequeueReusableCellWithIdentifier:@"AttributeCell"];


heightForRowAtIndexPath中计算可变高度单元格



我首先更新我的参考单元格的宽度,以便它显示其子视图(UILabels),然后我可以将其用于计算。然后我使用更新的标签宽度来确定标签的高度,并添加任何必要的单元格填充来计算我的单元格高度。 (请记住,只有当您的细胞与标签一起改变高度时,才需要进行这些计算)。


UIDeviceOrientation orientation = [**UIDevice currentDevice] orientation];
float width = UIDeviceOrientationIsPortrait(orientation) ? 320 : 480;

case TableSectionDescription:
    CGRect oldFrame = self.descriptionReferenceCell.frame;
    self.descriptionReferenceCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, width, oldFrame.size.height);
    CGFloat referenceWidth = self.descriptionReferenceCell.descriptionLabel.frame.size.width;
    CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(referenceWidth, MAXFLOAT) AndFont:self.descriptionReferenceCell.descriptionLabel.font];
    return newLabelFrameSize.height + kTextCellPadding;


cellForRowAtIndexPath中,不要依赖方向进行任何动态布局相关更新



willDisplayCell forRowAtIndexPath中更新标签高度



由于表格单元格已执行layoutSubviews,因此标签已经具有正确的宽度,我用它来计算标签的精确高度。我可以安全地更新我的标签高度。


case TableSectionDescription:
    CGRect oldFrame = ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame;
    CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(oldFrame.size.width, MAXFLOAT) AndFont:((CWProductDetailDescriptionCell *)cell).descriptionLabel.font];
    ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, newLabelFrameSize.height);
    break;


在旋转时重新加载数据



如果您不重新加载数据,您的可见单元格不会更新其高度和标签。


-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    [self.tableView reloadData];
}


唯一需要指出的是,我使用辅助类计算标签高度,只包含基本调用。真的不再保存任何代码了很多(我曾经在那里有其他东西),所以只是直接使用正常的方法你的字符串。


- (CGSize) sizeForString:(NSString*)string WithConstraint:(CGSize)constraint AndFont:(UIFont *)font {
    CGSize labelSize = [string sizeWithFont:font
                          constrainedToSize:constraint 
                              lineBreakMode:UILineBreakModeWordWrap];
    return labelSize;
}

其它参考1


我同意你的大部分意见。但有几点想法:


你说:



  cellForRowAtIndexPath中的cell.frame始终给出其大小
  肖像模式。 (即,对于iPhone应用程序,它总是报告320)。



这不是我的经历。在我的tableView:cellForRowAtIndexPath中,我只是放入了


NSLog(@"Cell frame.size.width=%f", cell.frame.size.width);


并且它根据方向报告320或480.因此,我没有通过将我的帧计算逻辑移动到willDisplayCell中看到任何好处。


你说:



  在heightForRowAtIndexPath中,我用。计算单元格高度
  tableView宽度和我已经抓住的百分比:



是的,你可以做到这一点。如果您在单元格右侧的描述可能很长,您可能希望将单元格左侧的标签保持固定宽度,并利用更宽的屏幕来真正最大化您所拥有的长描述细胞的右侧。但是如果你经常在右侧有较短的描述,那么根据屏幕宽度的目标百分比进行重新排列是个好主意。就个人而言,我可能只使用固定比率(例如,左边的标签总是占宽度的33%,右边的标签总是需要66%的单元格,而不是根据百分比转换像素),这可能会简化百分比逻辑,虽然它在功能上是等价的)。


你说:



  您无法利用您可能设置的自动调整遮罩和边距
  IB



是的,我从来没有尝试过(在此之前,我看到你描述的行为相同),但即使它确实有效,也不是一个好主意,因为你真的需要根据你使用的单词重新计算高度,所以虽然它可能会让你接近,但你仍然需要重新计算高度,无论如何都要把它弄好。 (坦率地说,我只是希望我可以说根据标签的字体和宽度以及文字自动调整高度,但这并不存在。)不过,我同意你的看法,它不起作用,这很奇怪更好。自动调整应该更优雅地处理它。


你说:



  如果你不这样做,你的可见细胞不会更新它们的高度和标签
  重新加载数据。



然后你建议调用reloadData。但是不是didRotateFromInterfaceOrientation太迟了,因为我看到我的肖像UILabels在它去景观之后立即闪现,但在此之前调用的景观重绘之前。我不想继承UITableViewCell并覆盖drawRect,但我不确定如何重新布局我的UILabel帧,然后再在横向绘制它们。我最初尝试将其移至willRotateToInterfaceOrientation并没有起作用,但也许我没有在那里做某事。

其它参考2


谢谢,这非常有帮助。不过,我不想在任何宽度上进行硬编码。我花了很长时间看这个问题,这就是我发现的。


基本上,有一个鸡和蛋的问题,因为表格视图需要知道单元格的高度才能进行自己的布局,但是在高度可以之前需要为包含表格视图的几何图形配置单元格确定。


打破这种循环依赖关系的一种方法是将宽度硬编码为320或480,这在本页的其他地方建议。不过,我不喜欢硬编码大小的想法。


另一个想法是使用原型单元预先计算所有高度。我尝试了这个,但仍然遇到计算错误的高度,因为创建一个UITableViewCell只会留下一个没有为表格视图的实际样式和大小配置的单元格。显然,没有办法手动配置用于特定表视图的单元格;你必须让UITableView去做。


我决定采用的策略是让表格视图按照正常情况执行所有操作,包括创建和配置自己的单元格,然后一旦设置完所有单元格,计算实际行高度并刷新表格视图。缺点是表格视图加载了两次(一次是默认高度,另一次是正确的高度),但好处是这应该是一个相当干净和强大的解决方案。





此示例假定您有一个实例变量_cellHeights,它是一个数组数组,用于保存每个表视图部分的计算行高:


@interface MyViewController () {
    NSArray *_cellHeights; // array of arrays (for each table view section) of numbers (row heights)
}


编写一种方法,根据当前单元格内容计算所有单元格高度:


- (void)_recalculateCellHeights
{
    NSMutableArray *cellHeights = [**NSMutableArray alloc] init];

    for (NSUInteger section=0; section<[self.tableView numberOfSections]; section++) {
        NSMutableArray *sectionCellHeights = [**NSMutableArray alloc] init];

        for (NSUInteger row=0; row<[self.tableView numberOfRowsInSection:section]; row++) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

            // Set cell's width to match table view's width
            CGRect cellFrame = cell.frame;
            cellFrame.size.width = CGRectGetWidth(self.tableView.frame);
            cell.frame = cellFrame;

            CGFloat cellHeight = self.tableView.rowHeight; // default

            if (cell.textLabel.numberOfLines == 0 || (cell.detailTextLabel && cell.detailTextLabel.numberOfLines == 0)) {
                [cell layoutIfNeeded]; // this is necessary on iOS 7 to give the cell's text labels non-zero frames

                // Calculate the height of the cell based on the text
                [cell.textLabel sizeToFit];
                [cell.detailTextLabel sizeToFit];

                cellHeight = CGRectGetHeight(cell.textLabel.frame) + CGRectGetHeight(cell.detailTextLabel.frame);

                // This is the best I can come up with for this :(
                if (self.tableView.separatorStyle != UITableViewCellSeparatorStyleNone) {
                    cellHeight += 1.0;
                }
            }

            [sectionCellHeights addObject:[NSNumber numberWithFloat:cellHeight**];
        }

        [cellHeights addObject:sectionCellHeights];
    }

    _cellHeights = cellHeights;
}


-tableView:cellForRowAtIndexPath:中,正常创建一个单元格并填写textLabel,而不执行任何布局(正如Bob在本页其他地方提到的那样:UITableViewCells在调用它之后立即执行所有布局工作,所以你的努力最好是徒劳无功,最糟糕的是他们搞砸了)。重要的是将标签s numberOfLines属性设置为0以允许动态高度。


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [**UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

        // Assign any fonts or other adjustments which may affect height here

        // Enable multiple line support so textLabel can grow vertically
        cell.textLabel.numberOfLines = 0;
    }

    // Configure the cell...
    cell.textLabel.text = [self.texts objectAtIndex:indexPath.section];

    return cell;
}


-tableView:heightForRowAtIndexPath:中,返回预先计算的单元格高度:


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (_cellHeights) {
        NSArray *sectionCellHeights = [_cellHeights objectAtIndex:indexPath.section];
        return [**sectionCellHeights objectAtIndex:indexPath.row] floatValue];
    }

    return self.tableView.rowHeight; // default
}


-viewDidAppear:中,触发重新计算单元格高度并刷新表格。请注意,这必须在-viewDidAppear:时发生,而不是-viewWillAppear:时间,因为在-viewWillAppear:时,尚未为表视图的几何配置单元格。


- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self _recalculateCellHeights];
    [self.tableView reloadData];
}


-didRotateFromInterfaceOrientation:中,做同样的事情:


- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self _recalculateCellHeights];
    [self.tableView reloadData];
}


-tableView:willDisplayCell:forRowAtIndexPath:中没有必要做任何特别的事情。