[英]Correct way to asynchronously load tableview cell data
我尝试异步设置UITableViewCell
'描述'字段,但是由于重用视图单元格,当我快速滚动tableview时遇到问题-多次刷新tableview单元格。 我的代码如下。 怎么了
- (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;
}
块
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]];
}
});
旁注:获取/设置字典值的主要方法是objectForKey
和setObject:forKey:
objectForKey
valueForKey:
和setValue:forKey:
仅用于键-值编码魔术。
我找到了一个优雅的解决方案,只需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];
}
我认为问题在于,仅在异步处理完成且成功时才填充_tableViewDescirptions
(滑稽名称BTW)字典。 因此,当单元快速滚动时,您将一次又一次地设置许多异步调用。
而是总是立即设置描述(例如,使用“ Loading ...”),并且只执行一次后台任务。 下次调用此方法时,它将不会尝试再次下载。 当然,如果下载失败,则需要回退。
另外,我认为与其构建昂贵的字符串哈希, indexPath
可以使用indexPath
作为键。
最后,在我看来,您在后台任务中所做的工作微不足道,可以轻松地在主线程中完成。
在您遵循我的答案之前,我想告诉您,以下代码对内存管理不利,因为它会为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;
}
如果行数有限,那么这是最适合您的代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.