我的核心数据模型有两个实体:Author
和Book
,具有To-Many关系(一个作者 - >多本书)。在主视图中,我显示了一个书籍列表,其中每个单元格包含书名和作者姓名。视图也分为几个部分,每个部分标题是作者名称。 (注意" author.name"为排序描述符和sectionNameKeyPath设置)
以下是代码(为简洁起见而简化):
- (NSFetchedResultsController *)fetchedResultsController {
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"author.name" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"author.name" cacheName:nil] autorelease];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
return __fetchedResultsController;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
Book* book = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", book.name, book.author.name];
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller {
[self.tableView reloadData];
}
现在,如果用户更改作者姓名然后返回主视图,则单元格和部分将显示旧作者姓名。在搜索互联网后,我发现以下代码修复了单元格中的旧作者姓名问题,但未在章节标题中修复:
- (void)saveAuthorName:(NSString *)newName {
for (Book* book in author.books) {
[book willChangeValueForKey:@"author"];
}
author.name = newName;
for (Book* book in author.books) {
[book didChangeValueForKey:@"author"];
}
// save changes
NSError * error ;
if( ![self.moc save:&error] ) {
// Handle error
}
}
为什么[self.fetchedResultsController sections]
仍包含旧作者姓名?请帮忙!
本节涉及Marcus的回应#1
嗯,还是有点模糊。你是说分段数不正确吗?
部分的数量没有改变。 Sections
属性数组中对象的内容不正确。
根据发布的代码,您只需从NSFetchedResultsController中检索NSManagedObject实例。或许对于那是什么感到困惑?
在代码中,我正在从NSManagedObject
检索NSFetchedResultsController
个实例,以便显示每个表格单元格的书名和作者名称(调用cellForRowAtIndexPath
时)。但是,UITableView
中每个部分的标题都不是从NSManagedObject
获取,而是来自_NSDefaultSectionInfo
实现NSFetchedResultsSectionInfo
协议的对象(titleForHeaderInSection
时)叫做)。
我通过以下为调试编写的代码实现了这一点:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id mySection = [[fetchedBooks sections] objectAtIndex:section];
NSLog(@"%@", mySection)
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
日志的结果是< _NSDefaultSectionInfo:0x8462b90>。 Sections属性的NSFetchedResultsController文档显示:
/* Returns an array of objects that implement the NSFetchedResultsSectionInfo protocol.
It's expected that developers use the returned array when implementing the following methods of the UITableViewDataSource protocol
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;
*/
如果我错了,请纠正我:_NSDefaultSectionInfo
不是NSManagedObject
对吗?如果是这样,当NSFetchedResultsController
更改后,_NSDefaultSectionInfo
如何检测NSManagedObject
个对象的更改?
因此,这会导致一些问题:
您如何在其他视图中更改作者姓名?
更改作者姓名的代码在上面saveAuthorName
中列出。应用程序中的流程如下:
从主UITableView
,选择使用导航控制器打开新图书视图的图书单元格。
从书籍视图中,选择使用导航控制器打开新的选择 - 作者视图的选择作者。 (所有作者都列在UITableView
)
从“选择 - 作者”视图中,选择使用导航控制器打开新编辑 - 作者视图的任何作者。
在编辑 - 作者视图中更改作者姓名并保存,以关闭视图并将上一个视图(选择 - 作者)带入导航控制器堆栈。
现在可以选择不同的作者并对其进行编辑等等。直到关闭此视图。 (带来书籍视图)
关闭书籍视图,显示所有书籍的主视图。
单元格中的作者姓名是旧的还是只是部分标题?
Cell完全更新了作者姓名(感谢willChangeValueForKey
和didChangeValueForKey
中调用的saveAuthorName
。只有章节标题是旧的。
你能指出哪一个确切吗?我在上面的代码部分编写了与我相关的所有委托方法。这包括:您的委托方法是什么样的?
cellForRowAtIndexPath
titleForHeaderInSection
controllerDidChangeContent
还需要其他方法吗?
从编辑返回后,您确定 - [UITableViewDatasource tableView:titleForHeaderInSection:]是否正在触发?
100%肯定。 titleForHeaderInSection
带来旧值,并在保存更改后调用它。 (cellForRowAtIndexPath
也会在保存更改后调用,但会带来新值)
NSFetchedResultsControllerDelegate方法在返回时触发了什么?
如果您的意思是保存(意味着,在调用saveAuthorName
之后),则调用以下方法:
controllerWillChangeContent:
(不使用它,仅用于调试信息)
controller:didChangeObject:
(不使用它,仅用于调试信息)
controllerDidChangeContent:
如果您的意思是在调用方法后返回主视图(意味着关闭Book视图):
cellForRowAtIndexPath
titleForHeaderInSection
numberOfSectionsInTableView
numberOfRowsInSection
感谢您的帮助。谢谢!
您正在实现-controller:didChangeSection:atIndex:forChangeType:?
是的,但是在更改作者姓名时不会被触发。 NSFetchedResultsController
的当前配置如下:
如果didChangeSection
配置如下,更改图书名称(而不是作者姓名)将触发NSFetchedResultsController
事件:
这意味着委托已正确挂钩到NSFetchedResultsController。
为了监控部分更改,更改作者姓名时[book willChangeValueForKey:@"author"]
和[book didChangeValueForKey:@"author"]
的调用不足NSFetchedResultsController
。
答案 0 :(得分:5)
通常,如果您正在为正在进行更改的NSManagedObjectContext
和NSFetchedResultsController
处理单个UIViewController
,则无需保存更改。
如果您有多个NSManagedObjectContext
,则不适用。
假设您有一个NSManagedObjectContext
,我会确保您在NSFetchedResultsController
上设置了委托,并在委托方法中设置了断点,以查看您是否收到任何回调。
我还会使用调试器并打印出您正在使用的NSManagedObject的指针,并确保它们是相同的。如果不是,那么它会指向NSManagedObjectContext
的问题。
根据您发布的代码,您只需从NSManagedObject
检索NSFetchedResultsController
个实例。或许对于那是什么感到困惑?
NSFetchedResultsController
只是一个包含一个或多个部分的容器。这些部分也只是一个容纳一个或多个NSManagedObject实例的容器。这些是您在应用程序中的任何其他位置访问的NSManagedObject
的相同的实例(假设设计为NSManagedObjectContext
)。
因此,如果您更改应用中的NSManagedObject
任何地方中的数据,它将在NSFetchedResultsController
中更新,因为它是同一个对象,而不是副本,但是完全相同的对象。
因此,这会导致一些问题:
-[UITableViewDatasource tableView: titleForHeaderInSection:]
正在解雇?NSFetchedResultsControllerDelegate
方法会触发什么?您是否正在实施-controller: didChangeSection: atIndex: forChangeType:
?如果没有,请这样做并告诉我它是否会发生火灾。如果它发射,它什么时候开火?在致电-[UITableViewDatasource tableView: titleForHeaderInSection:]
之前或之后?
这开始听起来像苹果虫。
有几点想法:
答案 1 :(得分:1)
今天使用Swift一切都变得更容易。
NSFetchedResultsControllerDelegate
NSFetchedResultsController
。performFetch
,它会吞并通常发给代表的活动。答案 2 :(得分:0)
为什么不提交保存更改的交易?
- (void)saveAuthorName:(NSString *)newName {
for (Book* book in author.books) {
[book willChangeValueForKey:@"author"];
}
author.name = newName;
for (Book* book in author.books) {
[book didChangeValueForKey:@"author"];
}
NSError * error ;
if( ![self.moc save:&error] ) {
..... do something ..
}
}
答案 3 :(得分:0)
我知道这是一个非常老的问题,但是在花了很多时间研究,测试并尝试为自己解决相同的情况之后,我想分享我的发现。我可以肯定这是一个Apple错误,并且我已经针对NSFetchedResultsSectionInfo
的name属性问题(并非总是具有正确的值)提交了反馈FB7960282。
我的Objc有点生锈,所以我将在Swift中提供解决方法。
// This is a workaround for an apparent bug where the name property of an NSFetchedResultsSectionInfo
// element sometimes provides an incorrect name for the section.
private func name(for section: NSFetchedResultsSectionInfo) -> String {
// The objects property of the NSFetchedResultsSectionInfo should always have at least one element
let object = section.objects?.first as! NSObject
// The name of the section should be the value of the Key Path of all objects in the array
let name = object.value(forKeyPath: self.frc.sectionNameKeyPath!) as? String ?? ""
return name
}
上面的函数假定self.frc是您的提取结果控制器。
顺便说一句,由于查询正在返回书籍,因此,如果您想对作者的更改做出反应,则应注意NSManagedObjectContextDidSave
通知。