如何使自定义单元格高度有效



最近我遇到了一个由heightForRowAtIndexPath:引起的效率问题。每次你必须画一个你讨厌的单元格来创建它并计算它的大小——它太长了!

有人会说,为什么不使用estimatedHeightForRowAtIndexPath:,但当你在这里通过估计时,你只是失去了准确性,所以当你滚动到顶部时,一些单元格根本看不见。。。

我想拓宽我对这件事的了解。推特、脸书或Instagram的人是怎么做到的?

我想你指的是一个单元格的高度由单元格的内容决定的情况。

通常,在这种情况下,单元的高度是在布局过程中计算的。如果不至少执行一次布局,就没有其他方法可以计算该高度。

例如,您有一个带有描述标签的单元格。您需要将整个描述显示为多行文本而不截断它。因此,您必须使描述标签的高度可变。

当tableview询问单元格的高度时,您必须计算描述文本所需的行数。这与您在单元格内容布局期间执行的工作完全相同。

在常见情况下,布局过程是一些轻量级计算。其思想是从单元格类中提取计算代码,并根据需要直接访问它们。

在以下示例中:

CellLayoutParams包含在计算布局时应考虑的初始信息。下面是应该由单元格和单元格宽度显示的数据。

@interface CellLayoutParams : NSObject {
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* description;
@property (nonatomic, assign) CGFloat width;
- (BOOL)isEqualToLayoutParams:(CellLayoutParams*)params;
@end

CellStyle是一个描述单元的样式属性的对象。例如,描述标签的字体、各种单元格填充、边距和插入。

@interface CellStyle : NSObject
@property(nonatomic, strong) UIFont* titleFont;
@property(nonatomic, strong) UIFont* descriptionFont;
- (struct CellLayoutInfo)layoutInfoForParams:(CellLayoutParams*)params;
@end
@implementation CellStyle
- (struct CellLayoutInfo)layoutInfoForParams:(CellLayoutParams*)params {
    CellLayoutInfo layoutInfo;
    const CGPoint contentOffset = CGPointMake(kLeftMargin, kTopMargin);
    const CGFloat contentWidth = params.width - kAccessoryWidth - kLeftMargin;
    NSString* descriptionValue = params.description;
    CGSize descriptionSize = [descriptionValue sizeWithFont: [self descriptionFont]
                                          constrainedToSize: CGSizeMake(contentWidth, CGFLOAT_MAX)];
    layoutInfo.descriptionFrame.size = descriptionSize;
    layoutInfo.descriptionFrame.origin = CGPointMake(contentOffset.x, layoutInfo.titleFrame.origin.y + layoutInfo.titleFrame.size.height + kVerticalInset);
    layoutInfo.preferredHeight = layoutInfo.descriptionFrame.origin.y + descriptionSize.height + kBottomMargin;
    return layoutInfo
}
@end

CellLayoutInfo是具有计算的布局信息的结构:计算的帧和优选的高度值。

typedef struct CellLayoutInfo {
    CGRect titleFrame;
    CGRect descriptionFrame;
    CGFloat preferredHeight;
} CellLayoutInfo;

您只需要执行布局计算即可获得首选高度:

- (CGFloat)tableView:(UITableView *)table heightForRowAtIndexPath:(NSIndexPath*)indexPath {
    MyData* dataItem = [self.data objectAtIndex:indexPath.row];
    CellLayoutParams* layoutParams = [CellLayoutParams new];
    layoutParams.description = dataItem.description;
    layoutParams.width = table.bounds.size.width;
    CellStyle* cellStyle = [CellStyle new];
    CellLayoutInfo layoutInfo = [cellStyle layoutInfoForParams:layoutParams];
    return layoutInfo.preferredHeight;
}

在单元格的layoutSubviews中,您可以访问相同的计算布局值:

- (void) layoutSubviews {
    [super layoutSubviews];
    CellLayoutParams* layoutParams = [CellLayoutParams new];
    layoutParams.description = self.descriptionLabel.text;
    layoutParams.width = self.bounds.size.width;
    CellStyle* cellStyle = [CellStyle new];
    CellLayoutInfo layoutInfo = [cellStyle layoutInfoForParams:layoutParams];
    self.titleLabel.frame = layoutInfo.titleFrame;
    self.descriptionLabel.frame = layoutInfo.descriptionFrame;
}

需要注意的几点:

  • 您不需要创建多个样式对象。样式对象是不可变的,并且对于所有单元格都是相同的
  • 布局代码不重复。表视图委托和单元格使用相同的布局代码
  • 您可以执行一些额外的优化:
    • 在控制器中缓存CellLayoutInfo对象并重用它们。当表格中有很多滚动时,这很有用
    • 在单元格中保留一份CellLayoutInfo和相应的CellLayoutParams,当单元格被重用时,只需检查参数是否发生了更改,以及是否应该计算新的CellLayoutInfo。当经常重新加载表视图时,这很有用

唯一的缺点是必须定义代码中的所有样式和布局。

最新更新