1

問題: 私は、推定された高さの行でインデックスパスと高さの行でインデックスパスを使用して計算されたセルの高さが間違った高さを返すという問題があります。具体的には、1 行のテキスト ラベルの高さがステッカーと同じ高さを返すことがあります (高さが大きすぎます)。または、画像が 1 行のテキスト ラベルと同じ高さになります (高さが小さすぎます)。

理論:メッセージの種類に応じて、異なるセルの高さを使用します。テキストがステッカーと同じ高さ (150 固定の高さ) として表示されることがあるため、セルが再利用されている場所にバグがあるのではないかと考えています。または、セルの高さのキャッシュを保存/読み取る方法にバグがあります。

報酬: 何が起こっているのかを把握するために、少なくとも 1 か月は費やしました。簡単な問題ではありません。あなたは私の永遠の感謝と、私がそれを追加できるときの報奨金を受け取ります.

これは、いくつかのコンテキストのコードです。まず、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];
    }
}

推定されたHeightForRowAtIndexPathとheightForRowAtIndexPathの両方に同じコードが使用されていることに注意してください。推定高さで重い計算を行うべきではないことはわかっています。ただし、セルを下から積み重ねており、高さが大きく異なる可能性があるため、この計算を行う (またはキャッシュから読み取る) 必要があります。そうしないと、一番下までスクロールする機能が機能しません (スクロールは、推定された高さの計算が不適切であると判断された場所にスクロールされます。これは、意味をなさないランダムな場所です)。

解決策またはアドバイスや提案をいただければ幸いです。

4

1 に答える 1

1

さて、キャッシュのことは赤いニシンでした。それは私のキャッシュではありません。私はそれを確信しています。

セルの高さが必要以上に高くなる問題のあるブロックを見つけました。

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

UILayoutFittingCompressedSize によって返される高さが正しくありません。この「状態」になると、すべてのテキスト セルがサイズ 150 に評価されます。新しいテキスト セルを何度追加しても。強制終了してアプリに戻ると、問題は解決します。

なぜこれが起こっているのか、私はまだ理解できません。スレッドの問題?細胞の再利用の問題?助言がありますか?

于 2015-09-22T06:11:05.377 に答える