Why is NSFetchedResultsController not updated with new data?

My Core Data model has two entities: Author and Book with the To-Many relationship (one author → many books). The main view displays a list of books in which each cell contains the name of the book and the name of the author. The presentation is also divided into sections, where each section title is the name of the author. (note that "author.name" is set for both the sort descriptor and sectionNameKeyPath)

Here is the code (simplified for clarity):

 - (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]; } 

Now, if the user changes the name of the author and then returns to the main view, the cells and sections will display the old name of the author. After searching the Internet, I found the following code that fixes the problem with the name of the old author in the cells, but not in the section headers:

 - (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 } } 

Why [self.fetchedResultsController sections] contain old author names? Please, help!

Update # 1

This section applies to Response # 1 of Marcus

Hmmm, still a little fuzzy. Are you saying that the number of partitions is incorrect?

The number of sections has not been changed. The contents of the objects in the Sections property array are incorrect.

Based on your code, you simply extract instances of NSManagedObject from NSFetchedResultsController. Perhaps there is some confusion about what it is?

In the code, I extract NSManagedObject instances from NSFetchedResultsController to display the book name and author name for each cell in the table (when cellForRowAtIndexPath is cellForRowAtIndexPath ). However, the titles of each section in a UITableView not accepted from NSManagedObject in my understanding, but are taken from the _NSDefaultSectionInfo object, which implements the NSFetchedResultsSectionInfo protocol (when titleForHeaderInSection is titleForHeaderInSection ).

I realized this with the following code that I wrote for debugging:

 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { id mySection = [[fetchedBooks sections] objectAtIndex:section]; NSLog(@"%@", mySection) return [[[self.fetchedResultsController sections] objectAtIndex:section] name]; } 

The log result was <_NSDefaultSectionInfo: 0x8462b90>. The NSFetchedResultsController documentation for the Sections property shows:

 /* Returns an array of objects that implement the NSFetchedResultsSectionInfo protocol. It 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; */ 

So please correct me if I am wrong: _NSDefaultSectionInfo not NSManagedObject right? If so, how NSFetchedResultsController detect changes for _NSDefaultSectionInfo objects when Author NSManagedObject changes?

So this leads to two questions:

How do you change the author name in this other view?

The code for changing the author’s name is written above in saveAuthorName . The flow in the application is as follows:

  • From the main UITableView , by selecting a book cell that opens a new book view using the navigation controller.

  • In book viewing mode, select an author who opens a new selection-author view using the navigation controller. (all authors are listed in UITableView )

  • In the "Select-author" window, select any author who opens a new "Editor-author" view using the navigation controller.

  • In the Editor-Author view, the author and save name change that closes the view and displays the previous Select-Author view in the navigation controller stack.

  • Now you can select another author and edit him, etc. before closing this view. (Leads a book look)

  • The “Closing a book” screensaver brings up the main view, where all books are displayed.

Is the author name in the cell old or just the section heading?

The cell is updated perfectly with the name of the author (thanks to willChangeValueForKey and didChangeValueForKey called in saveAuthorName ). The heading is old.

What do your delegate methods look like?

Could you indicate which one? I wrote all the delegate methods that look relevant to me in the code section above. It includes:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

Any other method required?

Are you sure your - [UITableViewDatasource tableView: titleForHeaderInSection:] is started after you return from editing?

100% sure. titleForHeaderInSection returns the old values ​​and is called after the changes are saved. ( cellForRowAtIndexPath also called after saving changes, but brings new values)

What methods of NSFetchedResultsControllerDelegate are triggered upon return?

If you mean when saving (means after calling saveAuthorName ), the following methods are called:

  • controllerWillChangeContent: (not using it, just for debugging information)

  • controller:didChangeObject: (not using it, just for debugging information)

  • controllerDidChangeContent:

If you want to return to the main view (meaning closing the book view), the following methods are called:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

I appreciate your help. Thanks!

Update # 2

Are you using -controller: didChangeSection: atIndex: forChangeType :?

Yes, but this does not work when changing the name of the author. The current configuration for NSFetchedResultsController as follows:

  • Essence: Book
  • Descriptor Sort: author.name
  • sectionNameKeyPath: author.name

Changing the name of the book (rather than the name of the author) will trigger the didChangeSection event when the NSFetchedResultsController configured as follows:

  • Essence: Book
  • Descriptor Sort: Name
  • sectionNameKeyPath: name

This means that the delegate is correctly connected to the NSFetchedResultsController.

It looks like calling [book willChangeValueForKey:@"author"] and [book didChangeValueForKey:@"author"] when changing the author name is not enough for NSFetchedResultsController to control section changes.

+6
source share
3 answers

Generally, you do not need to save changes if you are dealing with one NSManagedObjectContext for the NSFetchedResultsController and the UIViewController that make the changes.

This does not apply if you have more than one NSManagedObjectContext .

Assuming you have one NSManagedObjectContext , I would make sure you have the delegate set to NSFetchedResultsController and set breakpoints in the delegate methods to see if you really get any callbacks.

I would also use a debugger and print out the pointers for the NSManagedObject (s) you are working with, and make sure they are the same. If this is not the case, this indicates a problem with NSManagedObjectContext .

Answer # 1

Hmmm, still a little fuzzy. Are you saying that the number of partitions is incorrect?

Based on your code, you simply extract instances of NSManagedObject from NSFetchedResultsController . Perhaps there is some confusion about what it is?

NSFetchedResultsController is simply a container that has one or more sections. These sections also constitute a container containing one or more instances of NSManagedObject. These are the same NSManagedObject instances that you could access anywhere in the application (assuming you have one NSManagedObjectContext project).

Therefore, if you change the data in NSManagedObject anywhere in your application, it will be updated in NSFetchedResultsController because it is the same object, not a copy, but the same object.

So this leads to two questions:

  • How do you change the author name in this other view?
  • Is the author name in the cell old or just the section heading?
  • What do your delegate methods look like?
  • Are you sure your -[UITableViewDatasource tableView: titleForHeaderInSection:] started after you return from editing?
  • What NSFetchedResultsControllerDelegate methods fire on return?

Answer # 2

Do you do -controller: didChangeSection: atIndex: forChangeType: :? If not, do it and let me know if it works. If it works, when does it work? Before or after the call -[UITableViewDatasource tableView: titleForHeaderInSection:] ?

Answer # 3

It starts to sound like an Apple bug.

A few thoughts:

  • What happens if you run -performFetch: after the author tickled? I wonder if this will work.
  • I highly recommend you create a test case. Then you can send it to Apple for radar ( vital ), and I can also play with the test and see if there is a clean solution.
+5
source

Why aren’t you making transactions with changes saved?

 - (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 .. } } 
0
source

Swift is easier today.

  • You implement NSFetchedResultsControllerDelegate in your controller
  • Set your controller as a delegate to your NSFetchedResultsController .
  • Do not use performFetch on the fetchedResultsController , it swallows the event, which is usually dispatched to the delegate.
  • Modify managed object -> delegate methods should be called.
0
source

Source: https://habr.com/ru/post/909357/


All Articles