0

「説明」フィールドを非同期に設定しようとしましUITableViewCellたが、ビューセルを再利用しているため、テーブルビューを高速スクロールすると問題が発生します-テーブルビューセルが数回更新されます。私のコードは以下です。ここで何が問題なのですか?

   - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    {
        [TimeExecutionTracker startTrackingWithName:@"cellForRowAtIndexPath"];

        static NSString *CellIdentifier = @"MessageCell";

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

        CTCoreMessage *message = [_searchResults objectAtIndex:indexPath.row];
        UILabel *fromLabel = (UILabel *)[cell viewWithTag:101];
        UILabel *dateLabel = (UILabel *)[cell viewWithTag:102];
        UILabel *subjectLabel = (UILabel *)[cell viewWithTag:103];
        UILabel *descriptionLabel = (UILabel *)[cell viewWithTag:104];
        [subjectLabel setText:message.subject];
        [fromLabel setText:[message.from toStringSeparatingByComma]];
        [dateLabel setText:[NSDateFormatter localizedStringFromDate:message.senderDate
                                                          dateStyle:NSDateFormatterShortStyle
                                                          timeStyle:nil]];


        NSString *cellHash = [[NSString stringWithFormat:@"%@%@%@",fromLabel.text,dateLabel.text,subjectLabel.text] md5];

        if([_tableViewDescirptions valueForKey:cellHash] == nil){

            [descriptionLabel setText:@"Loading ..."];

            dispatch_async(backgroundQueue, ^{

                BOOL isHTML;
                NSString *shortBody = [message bodyPreferringPlainText:&isHTML];
                shortBody = [shortBody substringToIndex: MIN(100, [shortBody length])];
                [_tableViewDescirptions setValue:shortBody forKey:cellHash];

                dispatch_async(dispatch_get_main_queue(), ^{

                    [descriptionLabel setText:[_tableViewDescirptions valueForKey:cellHash]];

                });
            });
        }else{

            [descriptionLabel setText:[_tableViewDescirptions valueForKey:cellHash]];
        }


        [TimeExecutionTracker stopTrackingAndPrint];

        return cell;
    }
4

4 に答える 4

5

ブロック

dispatch_async(dispatch_get_main_queue(), ^{
    [descriptionLabel setText:[_tableViewDescirptions valueForKey:cellHash]];
});

の現在の値をキャプチャdescriptionLabelするため、ブロックが実行されると、その間にセルが別のインデックス パスに再利用されたとしても、そのラベルが更新されます。

したがって、代わりにセルをキャプチャし、セルの (現在の) インデックス パスが元の (キャプチャされた) インデックス パスと等しいかどうかを確認する必要があります。

また、メイン スレッドはデータ ソース_tableViewDescirptionsとして使用されるため、メイン スレッドでのみ更新する必要があります。

これは大まかに次のようになります (コンパイラはテストされていません)。

dispatch_async(dispatch_get_main_queue(), ^{
    [_tableViewDescirptions setValue:shortBody forKey:cellHash];
    if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
        UILabel *descriptionLabel = (UILabel *)[cell viewWithTag:104];
        [descriptionLabel setText:[_tableViewDescirptions valueForKey:cellHash]];
    }
});

補足:辞書の値を取得/設定する主な方法はobjectForKeysetObject:forKey:です。 valueForKey:Key-Value コーディング マジックにsetValue:forKey:のみ使用されます。

于 2013-08-10T08:55:58.137 に答える
2

わずか3ステップのエレガントなソリューションを見つけました(http://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.htmlから):

 // 1. Create a method for asynch. downloading tableview cell data:

    - (void) loadMessageDescription:(CTCoreMessage *)message forIndexPath:(NSIndexPath *)indexPath
    {
        dispatch_async(backgroundQueue, ^{

            BOOL isHTML;
            NSString *shortBody = [message bodyPreferringPlainText:&isHTML];
            shortBody = [shortBody substringToIndex: MIN(100, [shortBody length])];

            dispatch_async(dispatch_get_main_queue(), ^{

                UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

                [_messagesDescriptions setValue:shortBody forKey:[NSString stringWithFormat:@"%d",message.hash]];
                [(UILabel *)[cell viewWithTag:104] setText:shortBody];
                //[cell setNeedsLayout];
            });
        });
    }

//2. check for the tableview scrolling when get a tableview cell

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{

...

if (_tableView.dragging == NO && _tableView.decelerating == NO){

            [self loadMessageDescription:message forIndexPath:indexPath];
        }

...

// 3. When the scroll stopped - load only visible cells

- (void)loadMessageDescriptionForOnscreenRows
{
    NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
    for (NSIndexPath *indexPath in visiblePaths)
    {
        CTCoreMessage *message = [_searchResults objectAtIndex:indexPath.row];

        if([_messagesDescriptions valueForKey:[NSString stringWithFormat:@"%d",message.hash]] == nil){
            {
                [self loadMessageDescription:message forIndexPath:indexPath];
            }
        }
    }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (!decelerate){

        [self loadMessageDescriptionForOnscreenRows];
    }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self loadMessageDescriptionForOnscreenRows];
}
于 2013-08-10T13:04:22.030 に答える
1

_tableViewDescirptions問題は、非同期処理が完了して成功したときにのみ (変な名前 BTW) 辞書を埋めることだと思います。したがって、セルが高速スクロールで競合すると、多くの非同期呼び出しが何度も設定されます。

代わりに、常にすぐに説明を設定し (「読み込み中...」など)、バックグラウンド タスクを 1 回だけ実行します。次回このメソッドが呼び出されると、別のダウンロードは試行されません。もちろん、ダウンロードが失敗した場合はフォールバックが必要です。

indexPathまた、高価な文字列ハッシュを構築するのではなく、代わりに をキーとして使用することも可能だと思います。

最後に、バックグラウンド タスクで行っている作業は簡単で、メイン スレッドで簡単に実行できるように思えます。

于 2013-08-10T08:58:51.593 に答える
-2

私の回答に従う前に、次のコードは の行ごとに新しいセルを作成するため、メモリ管理に悪いことを伝えたいUITableViewので、注意してください。

ただし、UITableView行が制限されている場合(約50〜100行)、次のコードを使用することをお勧めします。ご都合がよろしければご利用ください。

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    NSString *CellIdentifier = [NSString stringWithFormat:@"S%1dR%1d",indexPath.section,indexPath.row];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil)
    {
        cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];

         /// Put your code here.
     }

      /// Put your code here.

    return cell;
}

行が限られている場合は、これが最適なコードです。

于 2013-08-10T08:41:19.427 に答える