ios - UITableView 上的单元格高度缓存和 [sizeToFit] *有时* 显示错误的高度

标签 ios objective-c uitableview heightforrowatindexpath

问题:我遇到一个问题,即使用 estimatedHeightForRowAtIndexPath 和 heightForRowAtIndexPath 计算的单元格高度返回错误的高度。具体来说,有时单行文本标签的高度返回与标签相同的高度(高度太大了)。或者,图像最终与单行文本标签的高度相同(高度太小了)。

理论:我根据消息的类型使用不同的单元格高度。由于有时文本显示为与贴纸(固定高度为 150)相同的高度,我想知道是否在重复使用单元格的地方存在错误。或者,我从单元格高度缓存中保存/读取的方式存在错误。

奖励:我花了至少一个月的时间试图弄清楚发生了什么。这不是一个容易的问题。您将收到我永恒的感激之情,以及我可以添加的赏金。

这是一些上下文的代码。首先,heightForRowAtIndexPath:

- (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath {

    NSNumber *cachedCellHeight = [self.cachedCellHeights objectForKey:[NSString stringWithFormat:@"%u", indexPath.row]];

    if([cachedCellHeight floatValue] >= MINIMUM_CELL_HEIGHT) {
        // NSLog(@"Retrieving cached cell height (estimatedHeightForRowAtIndexPath): %f for row: %u", [cachedCellHeight floatValue], indexPath.row);
        return [cachedCellHeight floatValue];
    }

    // Cell height is not in the cache (or somehow an invalid height is retrieved), calculate and store it
    NSManagedObject *record = [self.fetchedResultsController objectAtIndexPath:indexPath];

    NSInteger type = [[record valueForKey:@"type"] integerValue];
    NSString *message = [NSString stringWithFormat:@"%@", [record valueForKey:@"message"]];

    static NSString *identifier = @"MessagesTableViewCellTextLeft";

    switch (type) {               
        case TypeText:
            identifier = @"MessageTypeText";
            break;

        case TypeImage:
            identifier = @"MessageTypeImage";
            break;

        case TypeSticker:
            identifier = @"MessageTypeSticker";               
            break;
    }

    static MessagesTableViewCell *cell = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cell = [self.tableView dequeueReusableCellWithIdentifier: identifier];
    });

    CGFloat calculatedCellHeight;

    UIImage *image = nil;

    switch (type) {
        case TypeText:
            cell.textLabel.text = message;
            [cell setNeedsLayout];
            [cell layoutIfNeeded];
            calculatedCellHeight = [cell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize].height; // used for multi line labels
            break;

        case TypeImage:
            image = [self getImageFromDocumentsFolder:message];

            if (image) {
                calculatedCellHeight = [self getImageCellHeight: image]; // calculates height based on aspect ratio of image
            }

            break;
        case TypeSticker:
            calculatedCellHeight = 150;  // will always be this height
            break;
    }

    /* If the calculation fails and the value is below the minimum cell height, don't store in the cell heights cache */
    if(calculatedCellHeight >= MINIMUM_CELL_HEIGHT) {
        // NSLog(@"Calculating and storing cell height (estimatedHeightForRowAtIndexPath): %f for row: %u for type: %@", calculatedCellHeight, indexPath.row, identifier);
        [self.cachedCellHeights setObject:[NSNumber numberWithFloat:calculatedCellHeight] forKey:[NSString stringWithFormat:@"%u", indexPath.row]];
        [self saveCachedHeightsToDisk];
    } else {
        calculatedCellHeight = [cell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize].height;
    }

    return calculatedCellHeight;
}

此外,为了让多行标签起作用,我必须在自定义单元格类中设置一个 preferredMaxLayoutWidth:

- (void) updateConstraints {
    [super updateConstraints];

    // For multi-line labels, this property is required to word wrap appropriately
    // In this case, word wrapping will occur 100 units away from screen edge
    textLabel.preferredMaxLayoutWidth = CGRectGetWidth([[UIScreen mainScreen] bounds]) - 100;
}

这是我对单元格高度缓存的实现。注意,cachedCellHeights 只是一个合成的 NSDictionary:

- (void) saveCachedHeightsToDisk {
    NSArray *directoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    if ([directoryPath count] > 0) {
        NSString *rebasedPath = [ClientUtilities rebasePathToCurrentDocumentPath:CACHED_CELL_HEIGHTS_FILENAME];
        [self.cachedCellHeights writeToFile:rebasedPath atomically:YES];
    }
}

- (NSMutableDictionary *) loadCachedHeightsFromDisk {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *filePath = [ClientUtilities rebasePathToCurrentDocumentPath:CACHED_CELL_HEIGHTS_FILENAME];

    if (![fileManager fileExistsAtPath:filePath]){
        // NSLog(@"Initializing cached cell heights to disk");
        return [NSMutableDictionary dictionary];
    } else {
        // NSLog(@"Loading cached cell heights from disk");
        return [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
    }
}

请注意,同一代码用于 estimatedHeightForRowAtIndexPath 和 heightForRowAtIndexPath。我知道您不应该对 estimatedHeight 进行任何繁重的计算。但是,由于我是从底部堆叠单元格并且它们的高度可能有很大差异,所以我必须进行此计算(或从缓存中读取)。否则,滚动到底部功能将不起作用(它根据错误的估计高度计算将滚动放在它认为结束的位置,这是一些没有意义的随机点)。

将不胜感激解决方案或任何意见或建议。

最佳答案

好的,所以缓存的东西是一个红色的鲱鱼。这不是我的缓存。我确定。

我发现导致单元格高度比应有的高度高的违规 block :

cell.textLabel.text = message;
[cell setNeedsLayout];
[cell layoutIfNeeded];
calculatedCellHeight = [cell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize].height;
NSLog(@"Calculated cell height is: %f", calculatedCellHeight);

UILayoutFittingCompressedSize 返回的高度不正确。一旦进入这种“状态”,所有文本单元格的大小都会评估为 150。无论我添加多少次新的文本单元格。如果我强行关闭并返回应用程序,问题就解决了。

我仍然不明白为什么会这样。线程问题?细胞复用问题?有什么建议吗?

关于ios - UITableView 上的单元格高度缓存和 [sizeToFit] *有时* 显示错误的高度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32683797/

相关文章:

ios - 如何在键路径中更改 NSObject 的值?

ios - iTunes 帐户关联国家/地区

ios - '-' 的右操作数是垃圾值

IOS6.0, SupportedInterfaceOrientations in Navigation base application

ios - 在多个属性上对 NSArray 自定义对象进行排序

ios - 从类返回 NSURLConnection 数据

ios - 如何从本周的工作日名称获取具体日期

iphone - iOS 7 下 UITableView 部分索引相关的崩溃

ios - 添加更多单元格后,某些表格 View 单元格变得不可见。为什么?

swift - UITableViewCell ImageView 总是显示不同的数据