UITableView灵活/dynamic的heightForRowAtIndexPath

案件

通常你会使用cellForRowAtIndexPath委托方法来设置你的单元格。 为单元格设置的信息对于如何绘制单元格以及大小将是重要的。

不幸的是, heightForRowAtIndexPath委托方法在cellForRowAtIndexPath委托方法之前被调用,所以我们不能简单的告诉委托来返回单元格的高度,因为这个时候这个值将为零。

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

设想一个UITableViewCell ,它包含一个名为textLabel的标签。 在cellForRowAtIndexPath委托方法中,我们放置textLabel.numberOfLines = 0 ,它基本上告诉标签可以有多less行,因为它需要呈现特定宽度的文本。 如果我们给textLabel一个大于原来给tex​​tLabel的宽度的文本,就会出现问题。 第二行将出现,但单元格的高度将不会自动调整,所以我们得到一个搞砸的表视图。

如前所述,我们可以使用sizeWithFont来计算高度,但是需要知道使用哪种字体,宽度等。如果为了简单起见,我们只关心宽度,我们可以硬编码宽度将是约320.0(不考虑填充)。 但是,如果我们使用UITableViewStyleGrouped而不是简单的宽度会发生什么,然后将约300.0和细胞将再次混乱。 或者如果我们从纵向转换到横向会发生什么事情,我们有更多的空间,但是自从我们对300.0进行硬编码以来,它将不会被使用。

在这种情况下,你必须问自己这个问题,你可以避免硬编码多less。

我自己的想法

您可以调用属于UITableView类的cellForRowAtIndexPath方法以获取特定段和行的单元格。 我读了几个post,说你不想这样做,但我不是很明白这一点。 是的,我同意它已经分配了单元格,但是仅为要显示的单元heightForRowAtIndexPath委托方法,因此单元格将被分配。 如果正确使用dequeueReusableCellWithIdentifier则单元格将不会在cellForRowAtIndexPath方法中再次分配,而是使用指针,并且只调整属性。 那么问题是什么?

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

好吧,通过调用heightForRowAtIndexPath委托方法中的cellForRowAtIndexPath委托方法,我们收到了我们需要的有关单元格的所有信息,以确定它的大小。

也许你可以创build你自己的sizeForCell方法,通过所有的选项运行,如果单元格是Value1风格或Value2等等

结论/问题

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

是的,我同意它已经分配了单元格,但是仅为要显示的单元格调用heightForRowAtIndexPath委托方法,因此单元格将被分配。

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

我曾经这样做:

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

  2. 这是在我们从本地或远程源处理数据时完成的。

  3. 我预先确定将要使用的字体的types和大小,当我创build这个集合对象时。 您甚至可以存储UIFont对象或任何用于表示内容的自定义对象。

  4. 这些集合对象将被用于每次我实现UITableViewDataSource或UITableViewDelegate协议来确定UITableViewCell实例及其子视图的大小等。

通过这样做,您可以避免为了获取其内容的各种大小属性而必须inheritanceUITableViewCell。

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

如果我们把它旋转到任何方向,只要在运行时做一个resize的机制。 确保autoresizingMask设置正确。

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

这是我解决这个问题的方法

  1. 我假设在这个解决scheme中只有一个标签具有“dynamic”高度
  2. 我还假设如果我们使标签自动resize来拉伸高度随着细胞的生长,只有细胞高度需要改变
  3. 我认为笔尖有标签的位置和上面和下面有多less空间
  4. 我们不想每次更改标签中的标签的字体或位置时更改代码

如何更新高度:

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

问题:

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

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

关于单元格的高度:你可以检查你的tableView的宽度,如果有必要的话,减去UITableViewStyleGrouped的边距和最终的索引条和公开项(你在数据存储中寻找你单元格的数据)的宽度。 当tableView的宽度改变时,例如通过接口旋转,你必须调用[tableView reloadData]

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

你可以做dynamic的单元格高度,甚至可以使它们看起来相当不错,但要真正使它们看起来无缝,还需要做很多工作,如果要支持多个方向,则更是如此。

我有一个关于dynamic细胞高度的想法。

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

这样,如果你在heightForRowAtIndexPath方法内调用cellForRowAtIndexPath ,你将不会多次创build/自动释放单元格。

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

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

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

这是我用来为聊天应用程序实现一些比较光滑的单元格的解决scheme。

到目前为止,我一直对heightForCellAtIndexPath感到非常恼火,因为这导致违反了DRY原则。 有了这个解决scheme,我的heightForRowAtIndexPath:每个单元的成本为1.5ms,我可以将其削减到〜1ms。

基本上,你希望你的单元格内的每个子视图实现sizeThatFits:创build一个你configuration的离线单元格,然后用sizeThatFits:CGSizeMake(tableViewWidth,CGFLOAT_MAX)查询根视图。

一路上有几个陷阱。 一些UIKit视图具有昂贵的setter操作。 例如 – [UITextView setText]做了很多工作。 这里的技巧是创build一个子类,cachingvariables,然后重写setNeedsDisplay来在视图即将被渲染时调用 – [super setText:]。 当然,你必须实现你自己的sizeThatFits:使用UIKit扩展。