所有方向的UITableViewCell中的动态UILabel高度/宽度

时间:2012-04-20 01:25:53

标签: ios uikit

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

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

我认为这可能会走layoutSubviews的路线,但我不确定你如何将它与需要计算细胞高度的表结合起来。 Another interesting post在init上手动设置框架,以确保它们根据已知数量进行计算,但这只能解决部分问题。

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

Dynamic UILabel/Cell Height/Width

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

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

  • 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]

  6. 此方法存在问题:

    1. 您无法利用might set in IB的自动调整遮罩和边距
      • cellForRowAtIndexPath 返回单元格之后,这些似乎没有开始(不确定这是否完全正确)
      • 他们以某种方式弄乱了身高计算,并且你的标签在横向上比taller结束了{。{3}}。
    2. 如果您的标签宽度在desiredportrait中不是相同的百分比,则您需要知道这两个百分比
    3. 您需要知道/计算标签宽度百分比。理想情况下,您可以在自动调整大小的蒙版完成后立即计算这些内容。
    4. 感觉很笨拙。我有一种感觉,我会在编辑模式中遇到更多令人头疼的问题。
    5. 因此。这样做的正确方法是什么?我已经看过很多SO问题,但没有一个完全解决这个问题。

3 个答案:

答案 0 :(得分:12)

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

所以这是解决方案:

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

  1. 为每个需要调整高度的单元格保留原型单元格
  2. heightForRowAtIndexPath
    • 根据您所在的方向调整参考单元格宽度
    • 然后,您可以可靠地使用包含的UILabel框架宽度来计算高度
  3. 除初始设置外,不要在cellForRowAtIndexPath中进行任何帧调整
    • UITableViewCells在调用之后立即执行所有布局工作,所以你的努力充其量是徒劳的,最糟糕的是他们搞砸了
  4. willDisplayCell forRowAtIndexPath中进行框架调整
    • 我很生气我忘了这个
    • 它出现在所有UITableViewCell内部布局之后,就在它绘制之前
    • 您的带有自动调整遮罩的标签已经具有可用于计算的正确宽度
  5. 在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 :(得分:0)

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

你说:

  

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

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

你说:

  

如果您不知道,您的可见细胞不会更新其高度和标签   重新加载数据。

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

答案 2 :(得分:0)

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

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

打破这种循环依赖关系的一种方法是将宽度硬编码为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在调用它之后立即执行所有布局工作,所以你的努力充其量是徒劳的,最糟糕的是他们搞砸了“)。重要的是将标签的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:没有必要做任何特别的事。