UITableView灵活/动态heightForRowAtIndexPath

时间:2010-02-06 12:11:15

标签: iphone uitableview

案例

通常您会使用cellForRowAtIndexPath委托方法来设置您的单元格。为单元格设置的信息对于单元格的绘制方式以及大小的大小非常重要。

不幸的是heightForRowAtIndexPath委托方法在cellForRowAtIndexPath委托方法之前被调用,所以我们不能简单地告诉委托者返回单元格的高度,因为那时它将为零。 / p>

因此我们需要在表格中绘制单元格之前计算大小。幸运的是,有一种方法可以做到sizeWithFont,属于NSString类。然而,存在问题,为了动态地计算正确的大小,它需要知道如何呈现单元中的元素。我将在一个例子中说明这一点:

想象一下UITableViewCell,其中包含一个名为textLabel的标签。在cellForRowAtIndexPath委托方法中,我们放置textLabel.numberOfLines = 0,它基本上告诉标签它可以有多少行,因为它需要呈现特定宽度的文本。如果我们为textLabel提供一个大于最初赋予textLabel的宽度的文本,则会出现问题。第二行将出现,但单元格的高度不会自动调整,因此我们会看到一张混乱的表格视图。

如前所述,我们可以使用sizeWithFont计算高度,但需要知道使用哪个Font,宽度等等。如果为了简单起见,我们只关心宽度,我们可以硬编码宽度约为320.0(不考虑填充)。但是如果我们使用UITableViewStyleGrouped而不是普通的宽度那么将会发生大约300.0并且单元格将再次混乱。或者如果我们从纵向交换到横向,我们会有更多的空间,但是因为我们硬编码300.0所以它不会被使用。

在某些情况下,你必须问自己一个问题,你可以避免多少硬编码。

我自己的想法

您可以调用属于UITableView类的cellForRowAtIndexPath方法来获取特定部分和行的单元格。我读了几篇帖子说你不想那样做,但我真的不明白。是的,我同意它已经分配了单元格,但heightForRowAtIndexPath委托方法仅针对可见的单元格调用,因此无论如何都将分配单元格。如果正确使用dequeueReusableCellWithIdentifier,则不会在cellForRowAtIndexPath方法中再次分配单元格,而是使用指针并仅调整属性。那有什么问题呢?

请注意,单元格未在cellForRowAtIndexPath委托方法中绘制,当表格视图单元格变得可见时,脚本将调用UITableVieCell上的setNeedDisplay方法,该方法会触发drawRect方法绘制细胞。因此,直接调用cellForRowAtIndexPath委托不会失去性能,因为它需要绘制两次。

好的,通过在cellForRowAtIndexPath委托方法中调用heightForRowAtIndexPath委托方法,我们可以获得有关单元格的所有信息,以确定它的大小。

也许您可以创建自己的sizeForCell方法,该方法遍历所有选项,如果单元格为Value1样式,或者Value2等,则该怎么办。

结论/问题

这只是我在思想中描述的理论,我想知道我写的是否正确。或者也许还有另一种方法来完成同样的事情。请注意,我希望能够尽可能灵活地做事。

7 个答案:

答案 0 :(得分:26)

  

是的,我同意它已经分配了单元格,但是仅为可见的单元格调用heightForRowAtIndexPath委托方法,因此无论如何都将分配单元格。

这是不正确的。表视图需要为表视图中的所有行调用heightForRowAtIndexPath(如果已实现),而不仅仅是当前正在显示的行。原因是它需要计算出它的总高度才能显示正确的滚动指示器。

答案 1 :(得分:8)

我曾经这样做过:

  1. 根据将用于表视图的集合对象创建集合对象(大小信息数组(字典,行高的NSNumber等)。

  2. 当我们从本地或远程来源处理数据时,就完成了这项工作。

  3. 我在创建此集合对象时预先确定将使用的字体的类型和大小。您甚至可以存储UIFont对象或用于表示内容的任何自定义对象。

  4. 每次实现UITableViewDataSource或UITableViewDelegate协议时,都会使用这些集合对象来确定UITableViewCell实例及其子视图的大小等。

  5. 通过这种方式,您可以避免为了获取其内容的各种大小属性而必须继承UITableViewCell。

    不要使用绝对值来初始化帧。根据当前方向和边界使用相对值。

    如果我们将其旋转到任何方向,只需在运行时执行调整大小机制。确保正确设置了autoresizingMask。

    你只需要高度,你不需要在UITableViewCell内部的所有不必要的东西来确定行高。你可能甚至不需要宽度,因为我说宽度值应该相对于视图边界。

答案 2 :(得分:7)

以下是解决此问题的方法

  1. 我假设在此解决方案中只有一个Label具有“动态”高度
  2. 我还假设,如果我们使标签自动尺寸拉伸高度,因为细胞增长只需更改细胞高度
  3. 我假设笔尖有适当的间距,标签的位置和上面有多少空间并且明显低于它
  4. 我们不想在每次更改笔尖中标签的字体或位置时更改代码
  5. 如何更新身高:

    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    
        // We want the UIFont to be the same as what is in the nib,
        //  but we dont want to call tableView dequeue a bunch because its slow. 
        //  If we make the font static and only load it once we can reuse it every
        //  time we get into this method
        static UIFont* dynamicTextFont;
        static CGRect textFrame;
        static CGFloat extraHeight;
        if( !dynamicTextFont ) {
            DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
            dynamicTextFont = cell.resizeLabel.font;
            CGRect cellFrame = cell.frame;
            textFrame = cell.resizeLabel.frame;
            extraHeight = cellFrame.size.height-textFrame.size.height; // The space above and below the growing field
        }
        NSString* text = .... // Get this from the some object using indexPath 
    
        CGSize  size = [text sizeWithFont:dynamicTextFont constrainedToSize:CGSizeMake(textFrame.size.width, 200000.f) lineBreakMode:UILineBreakModeWordWrap];
    
        return size.height+extraHeight;
    }
    

    的问题:

    • 如果您没有使用原型单元格,则需要检查单元格是否为nil并初始化
    • 你的笔尖/故事板必须自动调整UILabel并将多行设置为0

答案 3 :(得分:4)

您应该查看Three20框架中的TTTableItemCell.m。它遵循一种不同的方法,基本上通过让每个单元类(使用一些预定义的设置,如字体,布局等)实现共享方法+ tableView: sizeForItem:(或类似的东西),在其中传递项目对象中的文本。当您查找特定单元格的文本时,您也可以查找相应的字体。

关于单元格高度:您可以检查tableView的宽度,并在必要时将边距减去UITableViewStyleGrouped,并将宽度减去最终索引栏和公开项目(您在单元格的数据存储中查找) '数据)。当tableView的宽度改变时,例如通过界面轮换,您必须调用[tableView reloadData]

答案 4 :(得分:3)

为了回答原始海报问的问题是“可以调用cellForRowAtIndexPath吗?”,问题不然。这会给你一个单元格,但它不会在内部将它分配给indexPath,也不会重新排队(没有方法可以将它放回去),所以你只会失去它。我想它会在自动释放池中并最终被释放,但你仍然会一遍又一遍地创建大量的单元格,这真的很浪费。

你可以做动态的单元格高度,你甚至可以让它们看起来很漂亮,但真正使它们看起来很无缝是很多的工作,如果你想支持多个方向等,还有更多工作。

答案 5 :(得分:3)

我对动态细胞高度有所了解。

只需创建自定义单元格的一个实例作为UITableViewController的成员变量。在tableView:heightForRowAtIndexPath:方法中设置单元格的内容并返回单元格的高度。

这样,如果您在cellForRowAtIndexPath方法中调用heightForRowAtIndexPath,就不会多次创建/自动释放单元格。

UPD:为方便起见,您还可以在自定义单元格类中创建静态方法,该方法将创建用于高度计算的单例单元格实例,设置单元格的内容,然后返回其高度。

tableView:heightForRowAtIndexPath:函数体现在看起来像这样:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [MyCell cellHeightForContent:yourContent];
}

答案 6 :(得分:1)

这是我用来为聊天应用程序实现一些相当光滑的单元格的解决方案。

到目前为止,我一直对heightForCellAtIndexPath感到非常恼火:因为它导致违反DRY原则。有了这个解决方案,我的heightForRowAtIndexPath:每个单元花费1.5毫秒,我可以减少到约1毫秒。

基本上,您希望单元格内的每个子视图都实现sizeThatFits:创建一个您配置的屏幕外单元格,然后使用sizeThatFits查询根视图:CGSizeMake(tableViewWidth,CGFLOAT_MAX)。

沿途有一些问题。一些UIKit视图具有昂贵的setter操作。例如 - [UITextView setText]做了很多工作。这里的技巧是创建一个子类,缓冲变量,然后覆盖setNeedsDisplay以在要渲染视图时调用 - [super setText:]。当然,你必须实现自己的sizeThatFits:使用UIKit扩展。