更新 2: iOS 14b2 对象删除作为删除和插入出现在快照中,并且 cellProvider 块被调用了 3 次! (Xcode 12b2)。
更新 1:animatingDifferences:self.view.window != nil 似乎是解决第一次与其他时间动画问题的好方法。
切换到获取控制器快照 API 需要做很多事情,但首先要回答您的问题,委托方法简单地实现为:
- (void)controller:(NSFetchedResultsController *)controller didChangeContentWithSnapshot:(NSDiffableDataSourceSnapshot<NSString *,NSManagedObjectID *> *)snapshot{
[self.dataSource applySnapshot:snapshot animatingDifferences:!self.performingFetch];
}
至于其他更改,快照不得包含临时对象 ID。因此,在保存新对象之前,您必须为其设置一个永久 ID:
- (void)insertNewObject:(id)sender {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
Event *newEvent = [[Event alloc] initWithContext:context];//
// If appropriate, configure the new managed object.
newEvent.timestamp = [NSDate date];
NSError *error = nil;
if(![context obtainPermanentIDsForObjects:@[newEvent] error:&error]){
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
abort();
}
if (![context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
abort();
}
}
您可以通过在快照委托中放置断点并检查快照对象以确保其中没有临时 ID 来验证此操作是否有效。
下一个问题是这个 API 非常奇怪,因为它无法从 fetch 控制器获取初始快照以用于填充表。对performFetch 的调用调用与第一个快照内联的委托。我们不习惯我们的方法调用导致委托调用,这是一个真正的痛苦,因为在我们的委托中,我们希望为更新而不是初始加载设置动画,如果我们为初始加载设置动画,那么我们会看到一个警告,表明表格正在更新而不在窗口中。解决方法是设置标志performingFetch,在performFetch 之前为初始快照委托调用设置为真,然后在之后设置为假。
最后,这是迄今为止最烦人的变化,因为我们不再可以更新表格视图控制器中的单元格,我们需要稍微打破 MVC 并将我们的对象设置为单元格子类的属性。获取控制器快照只是使用对象 ID 数组的部分和行的状态。快照没有对象版本的概念,因此它不能用于更新当前单元格。因此在cellProvider 块中,我们不更新单元格的视图,只设置对象。在那个子类中,我们要么使用 KVO 来监视单元格正在显示的对象的键,要么我们也可以订阅NSManagedObjectContextobjectsDidChange 通知并检查changedValues。但本质上,现在从对象更新子视图现在是单元类的责任。以下是 KVO 所涉及的示例:
#import "MMSObjectTableViewCell.h"
static void * const kMMSObjectTableViewCellKVOContext = (void *)&kMMSObjectTableViewCellKVOContext;
@interface MMSObjectTableViewCell()
@property (assign, nonatomic) BOOL needsToUpdateViews;
@end
@implementation MMSObjectTableViewCell
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit{
_needsToUpdateViews = YES;
}
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)setCellObject:(id<MMSCellObject>)cellObject{
if(cellObject == _cellObject){
return;
}
else if(_cellObject){
[self removeCellObjectObservers];
}
MMSProtocolAssert(cellObject, @protocol(MMSCellObject));
_cellObject = cellObject;
if(cellObject){
[self addCellObjectObservers];
[self updateViewsForCurrentFolderIfNecessary];
}
}
- (void)addCellObjectObservers{
// can't addObserver to id
[self.cellObject addObserver:self forKeyPath:@"title" options:0 context:kMMSObjectTableViewCellKVOContext];
// ok that its optional
[self.cellObject addObserver:self forKeyPath:@"subtitle" options:0 context:kMMSObjectTableViewCellKVOContext];
}
- (void)removeCellObjectObservers{
[self.cellObject removeObserver:self forKeyPath:@"title" context:kMMSObjectTableViewCellKVOContext];
[self.cellObject removeObserver:self forKeyPath:@"subtitle" context:kMMSObjectTableViewCellKVOContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == kMMSObjectTableViewCellKVOContext) {
[self updateViewsForCurrentFolderIfNecessary];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)updateViewsForCurrentFolderIfNecessary{
if(!self.window){
self.needsToUpdateViews = YES;
return;
}
[self updateViewsForCurrentObject];
}
- (void)updateViewsForCurrentObject{
self.textLabel.text = self.cellObject.title;
if([self.cellObject respondsToSelector:@selector(subtitle)]){
self.detailTextLabel.text = self.cellObject.subtitle;
}
}
- (void)willMoveToWindow:(UIWindow *)newWindow{
if(newWindow && self.needsToUpdateViews){
[self updateViewsForCurrentObject];
}
}
- (void)prepareForReuse{
[super prepareForReuse];
self.needsToUpdateViews = YES;
}
- (void)dealloc
{
if(_cellObject){
[self removeCellObjectObservers];
}
}
@end
还有我在 NSManagedObjects 上使用的协议:
@protocol MMSTableViewCellObject <NSObject>
- (NSString *)titleForTableViewCell;
@optional
- (NSString *)subtitleForTableViewCell;
@end
注意,我在托管对象类中实现keyPathsForValuesAffectingValueForKey,以在字符串中使用的键发生更改时触发更改。