我有一个UITableView + NSFetchedResultsController组合用于加载评论。评论包括姓名,日期,正文,重要的是头像图像。评论存储在核心数据中。
在viewDidLoad上,它会检查是否已从我的源加载了注释。如果尚未下载,则会生成NSOperation以下载并抓取HTML以获取每个评论的头像的名称,日期,正文和URL。它还为所有这些创建了核心数据实体。作为后台线程NSOperation,它使用私有NSManagedObjectContext来完成工作并将其保存回主MOC。
将这些添加到商店后,我的NSFetchedResultsController调用委托方法controllerWillChangeContent:,didChangeSection:,didChangeObject:和controllerDidChangeContent:。插入一批新注释时它可以正常工作,并将它们插入到表视图中就可以了。
我的表格视图单元格使用iOS8的UITableViewAutomaticDimension来获得动态高度,具体取决于每条评论的内容长度。如果我只显示每个评论的文本(暂时忽略头像图像),一切都按预期工作,滚动浏览评论工作正常,这看起来很普通。
然而,当它加载头像图像时我的问题就出现了。在我的configureCell :(由cellForRowAtIndexPath:和controller:didChangeObject :)调用,它检查图像是否已经下载。如果没有,它会产生一种新的,不同类型的NSOperation,以根据评论的avatarImageURL属性获取图像。获取后,它会将图像数据添加到Core Data实体中。它还通过自定义委派方法向表视图控制器发出信号,表明映像已更新。我的获取结果控制器还通过控制器发出我的表视图控制器视图:didChangeObject:。
这就是问题所在。
当滚动速度非常快时,这个数据第一次被下载并存储到Core Data中,表视图和动态调整大小的单元格非常奇怪。当向上和向下滚动时,它们会多次改变大小。只有在滚动整个单元格一次或两次之后,一切都很顺利。
当我完全禁用NSOperations的产生以下载头像时,一切都可以正常工作并且滚动得很好。但是,如果我让它下载图像并将它们添加到Core Data实体,但是无法实现自定义委托方法和控制器的NSFetchedResultsChangeUpdate情况:didChangeObject:,它仍然会导致最初的奇怪,不连贯的滚动。
我通过NSLogs注意到的一件事是,所有注释都在一个批处理中插入到表视图中,而每次下载单个头像时,获取的结果控制器将发出willChangeContent:,didChangeObject:和didChangeContent :。同样,这三种方法都适用于单个化身。
相关方法(如有必要,我可以添加更多):
- (void)viewDidLoad {
[super viewDidLoad];
// First get our current array of comic infos, initiating the controller
NSError *fetchError;
if (![[self fetchedResultsController] performFetch:&fetchError]) {
NSLog(@"Error in the fetched results controller: %@", fetchError.description);
return; // Don't do anything else.
}
self.tableView.estimatedRowHeight = 70.0f;
self.tableView.rowHeight = UITableViewAutomaticDimension;
if (self.comicInfo.comments == nil || self.comicInfo.comments.count == 0) {
CommentsRetrieverOperation *operation = [[CommentsRetrieverOperation alloc] initWithComicInfoID:self.comicInfo.objectID];
operation.managedObjectContext = self.managedObjectContext;
[self.commentDownloadQueue addOperation:operation];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CommentCell" forIndexPath:indexPath];
// Configure the cell...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
CommentTableViewCell *commentCell = (CommentTableViewCell *)cell;
Comment *comment = [self.fetchedResultsController objectAtIndexPath:indexPath];
commentCell.nameLabel.text = comment.name;
commentCell.dateLabel.text = comment.date;
commentCell.commentLabel.text = comment.content;
commentCell.commentLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
if ([comment isAdmin].boolValue == true) {
commentCell.contentView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundPattern1"]];
} else if (indexPath.row % 2 == 0) {
commentCell.contentView.backgroundColor = [UIColor colorWithRed:248.0f/255.0f green:248.0f/255.0f blue:248.0f/255.0f alpha:1.0f];
} else {
commentCell.contentView.backgroundColor = [UIColor whiteColor];
}
if (comment.avatarImage == nil || comment.avatarImage.image == nil) {
CommentImageDownloadOperation *operation = [[CommentImageDownloadOperation alloc] initWithCommentID:comment.objectID];
operation.managedObjectContext = self.managedObjectContext;
operation.delegate = self;
operation.userInfo = @{ @"cell":commentCell, @"indexPath":indexPath };
[self.avatarDownloadQueue addOperation:operation];
} else {
[self setAvatarImageForCell:commentCell withComment:comment];
}
commentCell.userInteractionEnabled = NO;
}
- (void)setAvatarImageForCell:(CommentTableViewCell *)cell withComment:(Comment *)comment {
if (comment.avatarImage.isDefaultImage.boolValue) {
cell.avatarImage = self.defaultAvatarImage;
} else {
UIImage *image = [UIImage imageWithData:comment.avatarImage.image];
cell.avatarImage = image;
}
}
- (void)commentImageDownloadOperation:(CommentImageDownloadOperation *)operation didFinishDownloadWithUserInfo:(id)userInfo {
NSIndexPath *indexPath = [self.tableView indexPathForCell:userInfo[@"cell"]];
if (indexPath == userInfo[@"indexPath"]) {
CommentTableViewCell *cell = userInfo[@"cell"];
Comment *tempComment = (Comment *)[self.managedObjectContext objectWithID:operation.commentID];
[self setAvatarImageForCell:cell withComment:tempComment];
}
}
#pragma mark - Fetched Results Controller Delegate
All of these methods are copied exactly from Apple's sample at https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/index.html#//apple_ref/doc/uid/TP40008228-CH1-SW10
所以很快,我的问题是,当在背景中为动态大小的单元格加载图像时,以及使用Core Data和获取的结果控制器时,滚动非常不连贯。有没有更好的方法来实现这一切,我是否实现了这个错误?有没有比一开始加载所有图像更好的方法?
编辑:一些澄清。当我在表视图中向下滚动时,获取的结果控制器会向对象发送更新。因此,当我向下滚动willChangeContent:时,didChangeObject:for type = NSFetchedResultsChangeUpdate,并为进入视图的每个新单元调用didChangeContent:。