【问题标题】:Dynamic heightForRowAtIndexPath: with many (thousands) of cells动态 heightForRowAtIndexPath:包含许多(数千)个单元格
【发布时间】:2012-08-27 16:11:37
【问题描述】:

在heightForRowAtIndexPath: 中使用动态值时遇到一些性能问题 (我知道这是这种方法,因为如果我设置一个静态值,响应能力会大大提高)。我的表格包含大约 3000 个单元格。

我明白为什么使用动态值时性能会受到影响(主要是因为方法中的计算必须对表格中的每个单元格执行一次才能显示数据),但我不知道如何制作效率更高。

在我遇到的许多类似问题中,建议的解决方案是在heightForRowAtIndexPath: 中使用NSString 的sizeWithFont 方法来加快速度。我目前这样做,但表仍然需要大约 1.5 秒来加载(并重新加载,这有点频繁)。这太长了,我需要优化它。

我目前正在使用的代码(至少它的本质)如下:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell;
    UILabel *label = nil;

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

        label = [[UILabel alloc] initWithFrame:CGRectZero];
        // set up label..
        [[cell contentView] addSubview:label];

    }

    NSDictionary *dict = alphabetDict; //dictionary of alphabet letters (A-Z). each key contains an NSArray as its object
    CGFloat rightMargin = 50.f; //padding for the tableview's index titles

    NSString *key = [[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
    NSArray *array = [dict objectForKey:key];
    NSString *cellText = [array objectAtIndex:indexPath.row];

    //TABLE_WIDTH is 268.f, CELL_MARGIN is 14.f
    CGSize constraintSize = CGSizeMake(TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, 20000.0f);
    CGSize labelSize = [cellText sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
    [label setFrame:CGRectMake(CELL_MARGIN, CELL_MARGIN, TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, labelSize.height)];
    [label setText:cellText];

    return cell;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //TODO make this faster

    NSDictionary *dict = alphabetDict;
    CGFloat rightMargin = 50.f;

    NSString *key = [[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
    NSArray *array = [dict objectForKey:key];
    NSString *cellText = [array objectAtIndex:indexPath.row];

    CGSize constraintSize = CGSizeMake(TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, 20000.0f);
    CGSize labelSize = [cellText sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];

    return labelSize.height + (CELL_MARGIN * 2) + 16.f;  
}

有人能指出正确的方向来进一步简化此代码吗?谢谢!

【问题讨论】:

    标签: objective-c uitableview


    【解决方案1】:

    我以前也遇到过类似的情况。我的解决方案是在执行 [tableView reloadData] 之前进行所有计算并将高度存储在名为 heightsArray 的 NSMutableArray 中。那么

    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
       [heightsArray objectAtIndex:indexpath.row];
    }
    

    上下滚动时不计算。从数组中读取。由于预先进行了计算,最初的延迟会更多,但就滚动时的性能提升而言,这是值得的。您还可以在后台线程上执行计算,从而节省处理时间。只需在您可以在后台线程上调用并准备您的高度数组的方法中完成所有这些操作。

    【讨论】:

    • 好建议,但此代码假定表格只有一个部分,并且不会以任何方式对其进行操作(即不会添加或删除任何单元格) - 否则您将不得不重新计算每次发生的高度。但这仍然是一个聪明的把戏。
    • 删除单元格时,从heightsArray中删除对应的高度。添加单元格时,请进行高度计算并将其添加到 heightsArray。与原始问题相比,我认为这些操作应该是相对便宜的操作。
    • 你是对的。这很好用。初始加载有点慢,但我会用活动指示器来娱乐用户。现在滚动和添加/删除单元格非常快。谢谢crypticcoder。
    【解决方案2】:

    heightForRowAtIndexPath中最耗时的部分是:

    [[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
    

    所以,您可以先添加一个属性sortedKeys,然后像这样使用它:

    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        NSString *key = [sortedKeys objectAtIndex:indexPath.section];
        CGFloat rightMargin = 50.f;
        // ...
    }
    

    【讨论】:

    • 感谢您的建议 - 我实施了更改,它似乎已经缩短了几毫秒,但加载时间仍然不是我想要的。还有其他建议吗?我不可能让它尽可能高效?
    【解决方案3】:

    crypticcoder 有一个很好的建议。我会建议一种变体,它可能更灵活,并且支持表更改(插入、删除、重新排序)。

    创建一个作为 UITableViewCell 子类的类,并为其添加一个 cellHeight 属性和 ivar cellHeight_。这样合成:

    @synthesize cellHeight=cellHeight_;
    

    在类中使用此代码以惰性方式计算单元格高度:

    -(CGFloat) cellHeight{
        if (cellHeight_ != 0)
            return cellHeight_;
        else {
            NSDictionary *dict = alphabetDict;
            CGFloat rightMargin = 50.f;
    
            NSString *key = [[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
            NSArray *array = [dict objectForKey:key];
            NSString *cellText = [array objectAtIndex:indexPath.row];
    
            CGSize constraintSize = CGSizeMake(TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, 20000.0f);
            CGSize labelSize = [cellText sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
    
            cellHeight_ = labelSize.height + (CELL_MARGIN * 2) + 16.f; 
            return cellHeight_; 
        }
    }
    

    和 sch 一样,我不喜欢里面的那种,但如果这不是性能问题,我就把它留在那里。

    如果要更改单元格的内容,请确保将 cellHeight_ 重置为零,以便延迟重新计算(再次需要时)。

    【讨论】:

    • 懒惰计算是什么意思。这是否意味着在滚动时或在后台线程或新生成的线程上?
    • 惰性计算意味着直到需要结果才进行计算。这是一种避免在一个时间段内发生大量配置的好方法,但是将计算分散到需要时发生。 (如果一次都需要它们,那么不会更好也不会更糟。)一旦计算出来,它就会被保存,在这种情况下,作为类中的 ivar。在我的示例中,使用零值表示该值尚未计算。 (我认为假设没有像元高度为零是安全的。)
    • 我在if语句中犯了一个错误。我会纠正的。 (应该是 if (cellHeight !=0)。我也得到了 cellheight 的返回声明。我也添加了这一点。如果造成任何混淆,我很抱歉。
    猜你喜欢
    • 1970-01-01
    • 2018-08-08
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-08
    • 2011-06-16
    相关资源
    最近更新 更多