更改谓词后,NSFetchedResultsController 的委托不会触发

Posted

技术标签:

【中文标题】更改谓词后,NSFetchedResultsController 的委托不会触发【英文标题】:NSFetchedResultsController's delegate doesn't fire after predicate is changed 【发布时间】:2012-11-08 22:39:36 【问题描述】:

我已经解决这个问题几个小时了,到目前为止没有成功。我在这里阅读了所有可能与我的问题有关的问题。

我有两个实体(A,B),它们通过一对多的关系以树状结构相互连接,如下所示:A > B。

有一个由 NSFetchedResultsController 支持的 UITableViewController 来显示与实体 A 的选定对象相关的实体 B 的所有对象。我为此使用谓词:

    - (NSFetchedResultsController *)fetchedResultsController

if (_fetchedResultsController != nil) 
    return _fetchedResultsController;


NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"B" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

[fetchRequest setFetchBatchSize:20];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"controlDate" ascending:NO];
NSArray *sortDescriptors = @[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];

NSMutableArray *subpredicates = [NSMutableArray array];
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"isRelatedTo = %@", selectedObjectOfA];
[subpredicates addObject:predicate1];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"syncStatus != %d", ObjectDeleted];
[subpredicates addObject:predicate2];

NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
[fetchRequest setPredicate:predicate];

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;

NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();


return _fetchedResultsController;

添加、删除和编辑对象一切正常。该表会相应更新。我的类符合 NSFetchedResultsControllerDelegate 并且委托方法是样板。

但我希望用户能够通过点击“向上”/“向下”按钮来循环浏览实体 A 的所有对象,而无需返回导航堆栈(就像可以直接在邮件中循环浏览电子邮件一样)应用程序)。 B 的相关对象的数量取决于 A 的选定对象(介于无和假设 20 之间)。

问题来了: 我的意图是通过更改谓词来刷新 FRC 的结果(不是它的结构或语法,而只是 'selectedObjectOfA' 的值并调用

[self.fetchedResultsController performFetch:&error];

其余的都应该由 FRC 完成。

第一个问题是:这种方法正确吗?

以下是我已经尝试过的场景:

只需将 'selectedObjectOfA' 设置为新值并调用 performFetch 总是显示相同的结果(因为我想 fetchRequest 和谓词没有被更新)

像这样“重置”FRC的fetchrequest

NSMutableArray *subprediactes = [NSMutableArray array];
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"isRelatedTo = %@",    self.selectedObjectOfA];
[subprediactes addObject:predicate1];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"syncStatus != %d", ObjectDeleted];
[subprediactes addObject:predicate2];

NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subprediactes];
[self.fetchedResultsController.fetchRequest setPredicate:predicate];

更新 FRC 并返回正确的数据,但应用程序因行数不正确而崩溃

'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

因为没有调用 NSFetchedResultsController 委托方法。因此 tableView 中的行数不适应新结果(返回的对象数根据错误消息是正确的)。

那么第二个问题是:为什么代理在添加/删除对象时触发,而不是在谓词(以及结果)改变之后?

提前谢谢你。

【问题讨论】:

【参考方案1】:

你必须打电话

[tableView reloadData];

修改获取请求后。获取的结果控制器仅跟踪在 performFetch 之后发生的更改。

您可以尝试以下方法来获得表格视图的动画更新(但我还没有尝试过,所以我不确定它是否有效):

致电[tableView beginUpdates]。 使用[tableView deleteRowsAtIndexPaths:...] 删除所有“旧”行。 更新获取的结果控制器获取请求。 致电[fetchedResultsController performFetch:...]。 为[fetchedResultsController fetchedObjects] 的所有对象调用[tableView insertRowsAtIndexPaths:...] 以插入所有“新”行。 致电[tableView endUpdates]

【讨论】:

你是对的。同时我也试过这个,它有效!不幸的是,带有[tableview beginUpdates][tableView endUpdates] 的行动画(添加/删除行)在这种情况下丢失了。有保留的可能吗?如果我说得对,更改谓词并执行新的 fetch 会重新启动 FRC 对更改的跟踪,这意味着不可能有“平滑”转换(因为没有调用委托)。你能确认一下吗? @FranzBernt:没有“内置”方法来更改 FRC 获取请求以及动画表视图更新。 - 我知道如何实现这一点(请参阅更新的答案),但我不确定这是否可行。 @FranzBernt:如果更新方法有效,请告诉我。 (如果没有,您将不得不使用reloadData)。 抱歉耽搁了。实际上,这几乎就是我在提问之前的情况。唯一的区别是我计算了新旧获取结果之间的行差异,并相应地添加/删除了行。这成功了。但我觉得这不是“正确的”——“用脚”做繁重的工作,让内置功能或多或少未被使用。因此我的问题。按照您的要求:未经测试,我很确定您建议的更新方法会起作用。所以,你会得到复选标记(即使答案不是我希望听到的;)【参考方案2】:

在谓词更改后为您的 tableview 设置动画:

NSFetchRequest* fetchRequest = self.fetchedResultsController.fetchRequest;
fetchRequest.predicate = newPredicate;

NSArray* objectsBefore = self.fetchedResultsController.fetchedObjects;

[self.fetchedResultsController performFetch:nil];

NSArray* objectsAfter = self.fetchedResultsController.fetchedObjects;


[self.tableView beginUpdates];

if (objectsBefore.count > 0)

    for (id objectBefore in objectsBefore)
    
        if ([objectsAfter indexOfObject:objectBefore] == NSNotFound)
        
            NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[objectsBefore indexOfObject:objectBefore] inSection:0] ;

            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
        
    


if (objectsAfter.count > 0)

    for (id objectAfter in objectsAfter)
    
        if ([objectsBefore indexOfObject:objectAfter] == NSNotFound)
        
            NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[objectsAfter indexOfObject:objectAfter] inSection:0];

            [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
        
    


[self.tableView endUpdates];

【讨论】:

以上是关于更改谓词后,NSFetchedResultsController 的委托不会触发的主要内容,如果未能解决你的问题,请参考以下文章

当 UserDefaults 值更改 SwiftUI 时,@FetchRequest 谓词不会更新

NSFetchedResultsController 对谓词更改的委托方法调用

从 setEditing 更改核心数据谓词

如何根据用户输入使用新谓词重新运行@FetchRequest?

如何刷新/重新加载以更新对谓词的更改并因此获取请求?

关闭模态视图控制器后自动释放崩溃