Кэширование высоты ячейки и [sizeToFit] в UITableView * иногда * отображают неверную высоту
Проблема: у меня возникла проблема, когда вычисленная высота ячейки с помощью оцененным значением HeightForRowAtIndexPath и 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;
}
Кроме того, чтобы заставить работать многострочные метки, я должен был установить предпочитаемый параметр MaxLayoutWidth в пользовательском классе ячеек:
- (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];
}
}
Обратите внимание, что один и тот же код используется как для оценочного значения HeightForRowAtIndexPath, так и для heightForRowAtIndexPath. Я знаю, что вы не должны делать какие-либо тяжелые вычисления в расчете высоты. Однако, так как я складываю ячейки снизу, и они могут иметь разную высоту, я должен выполнить этот расчет (или прочитать из кэша). В противном случае функция прокрутки до нижней части не работает (она помещает прокрутку туда, где, по ее мнению, конец основан на неверном расчете предполагаемой высоты, что является случайным местом, которое не имеет смысла).
Решение или любые советы или предложения будут с благодарностью.
1 ответ
Итак, в тайнике была красная сельдь. Это не мой кеш. Я уверен в этом.
Я обнаружил, что нарушающий блок, который вызывает высоту ячейки, намного выше, чем должен:
cell.textLabel.text = message;
[cell setNeedsLayout];
[cell layoutIfNeeded];
calculatedCellHeight = [cell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize].height;
NSLog(@"Calculated cell height is: %f", calculatedCellHeight);
Высота, возвращаемая UILayoutFittingCompressedSize, неверна. Как только он попадает в это "состояние", все текстовые ячейки оцениваются до размера 150. Сколько бы раз я не добавлял новую текстовую ячейку. Если я закрою приложение и вернусь в приложение, оно решит проблему.
Я до сих пор не могу понять, почему это происходит. Проблема с потоками? Вопрос повторного использования клетки? Какие-либо предложения?