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:并且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)字典。因此,当单元格快速滚动时,您会一次又一次地设置大量异步调用。

相反,请始终立即设置描述(例如,使用“正在加载...”)并且只执行一次后台任务。下次调用此方法时,它不会尝试再次下载。当然,如果下载失败,您需要后备。

此外,我认为与其构建昂贵的字符串哈希,不如将其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 回答