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

时间:2014-04-10 08:18:19

标签: ios objective-c uitableview

最近我遇到了由heightForRowAtIndexPath:引起的效率问题。每次你必须绘制单元格你的书呆子来创建它并计算它的大小 - 它太长了!

有人会说,为什么不使用estimatedHeightForRowAtIndexPath:,但是当你在这里通过估算时,你只是松散的准确性,所以当你滚动到顶部时,一些细胞根本看不到...

我想扩大我对此事的了解。来自Twitter,Facebook或Instagram的人是如何做到的?

1 个答案:

答案 0 :(得分:1)

我想你的意思是指细胞高度由细胞内容决定的情况。

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

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

当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的副本,并在重复使用单元格时,检查params是否更改以及是否应计算新的CellLayoutInfo。这在经常重新加载表视图时很有用。

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