AQGridView backed by a NSFetchedResultsController - objective-c

I'm trying to implement a AQGridView that uses a fetched results controller as its datasource.
I'm not particular sure how to handle the NSFetchedResultsController delegate methods using the grid view; namely the content changing ones. I understand how to use the FRC for the other grid view datasource delegates.
Could somebody point me in the right direction?

The result should look a little something like this:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[gridView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
break;
case NSFetchedResultsChangeDelete:
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
ChannelPageViewController *currentPageController, *destinationPageController;
NSIndexSet * indices = [[NSIndexSet alloc] initWithIndex: indexPath.row];
NSIndexSet *newIndices = [[NSIndexSet alloc] initWithIndex:newIndexPath.row];
switch(type) {
case NSFetchedResultsChangeInsert:
[gridView insertItemsAtIndices:newIndices withAnimation:AQGridViewItemAnimationNone];
break;
case NSFetchedResultsChangeDelete:
[gridView deleteItemsAtIndices:indices withAnimation:AQGridViewItemAnimationNone];
break;
case NSFetchedResultsChangeUpdate:
[gridView reloadItemsAtIndices:indices withAnimation:AQGridViewItemAnimationNone];
break;
case NSFetchedResultsChangeMove:
[gridView deleteItemsAtIndices:indices withAnimation:AQGridViewItemAnimationNone];
[gridView insertItemsAtIndices:newIndices withAnimation:AQGridViewItemAnimationNone];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[gridView endUpdates];
if ([[frc fetchedObjects] count] == 1) {
[gridView reloadData];
}
}

Since AQGridView doesn't have sections, the best way to handle it would be to implement the NSFethcedresultscontroller delegate methods, and ignore any code for the cases related to updated sections. Also make sure you initialize the fetchrequest with no sectionNameKeyPath.
Then just follow the normal pattern for updating rows, but using NSIndexSet instead of NSIndexPath and InsertItemAtIndicies/DeleteItemAtIndicies instead of insertRowAtIndexPath/deleteRowAtIndexPath
I'm moving my AQGridView to CoreData now, so I'll post any updates to my answer as soon as I'm done...

When the content changes I do a
[self.gridView reloadData];
or something like that in your case. It is exactly the same as with a tableview.

Related

No visible interface for 'ToDoListTableViewController' declares the selector 'configureCell: atIndexPath:'

I have created a class 'ToDoListTableViewController' with superclass 'UITableViewController' and have tried implementing this piece of code
[self configureCell:((ToDoTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]) atIndexPath:indexPath];
but I am getting the error that is in the title of this question. This is the full method in which it is in
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
case NSFetchedResultsChangeInsert:
{
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeDelete:
{
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeUpdate:
{
[self configureCell:((ToDoTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]) atIndexPath:indexPath];
break;
}
case NSFetchedResultsChangeMove:
{
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
and this is the beginning of the implementation file
#import "ToDoListTableViewController.h"
#import <CoreData/CoreData.h>
#import "ToDoTableViewCell.h"
#interface ToDoListTableViewController ()<NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#end
#implementation ToDoListTableViewController
in code I have found online this method is supposed to work but I am getting this error, can someone help me please?
You have to create the method configureCell:atIndexPath: before using it!. If you are following an online tutorial maybe you forgot to create it. Usually people create this method to isolate the setting of the cell properties from the rest of the creation of the cell itself. For example for the case when you try to calculate the height of the cell based on its contents using autolayout.

NSFetchedResultsController - Serious application error

In my app I have NSFetchedResultsController which update my table in every change in the DB.
Sometime during change something went wrong in the app and the table remain empty no matter what I do (even if I change screen and return, the table remain empty).
Only after turn off the app and turn it back on everything seems to be fine again.
I isolate the problem to this error
"CoreData: error: Serious application error. An exception was caught
from the delegate of NSFetchedResultsController during a call to
-controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section
after the update (19) must be equal to the number of rows contained in
that section before the update (19), plus or minus the number of rows
inserted or deleted from that section (3 inserted, 0 deleted) and plus
or minus the number of rows moved into or out of that section (0 moved
in, 0 moved out). with userInfo (null)"
It seems to be related to an un explain difference between the row number in "begin update" and "end update",which mean something when wrong when deleting or inserting rows.
This is kind of hard to debug using break point because in happens not very often.
Any suggestions?
my code :
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.guiTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.guiTableView beginUpdates];
[self.guiTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.guiTableView endUpdates];
break;
case NSFetchedResultsChangeDelete:
[self.guiTableView beginUpdates];
[self.guiTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationLeft];
[self.guiTableView endUpdates];
// if (controller.sections.count==0) {
// [self alertAboutNothingInMyAssetsAndPop];
// }
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.guiTableView;
SOOptionCell *cell;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
break;
case NSFetchedResultsChangeUpdate:
cell = (SOOptionCell *)[tableView cellForRowAtIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath animated:YES];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[self.guiTableView endUpdates];
}
First, remove all of your -beginUpdates and -endUpdates calls inside of the delegate methods. There should only be one of each. One -beginUpdates in the -controllerWillChangeContent: and one -endUpdates in the -controllerDidChangeContent:. I would go so far as to say that you should change the other methods back to the examples provided by Apple instead of writing them yourself.
Once you do that does the discrepancy go away? I suspect it will.

How to inform NSFetchedResultsController about [NSManagedObjectContext save]

I've the following question in Objective-C. When I delete an object from my ManagedObjectContext an saved this change, how can I inform my fetchedResultsController to perform a new fetch? In my understanding it is not correct to send performFetch-Message agian.
Thanks!
using this delegate methods you can make it posible
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[self managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
And set the value of the parameter cacheName to nil. it avoids crash and keeps only current data after ManagedObjectContext change
#pragma mark -
#pragma mark Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableViews = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableViews insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableViews deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[_delegate configureCell:[tableViews cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableViews deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableViews insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionMiddle
animated:NO];
}
this code automatically update and reload when you add,delete or update the data in NSFetchedResultsController and UITableViewController
you can use postNotificaton for which you should already have an observer set in your controller which will initiate the fetch request again.

NSFetchedResultsController objectAtIndex, objectAtIndexPath, indexPathForObject inconsistencies

I have a fetched result with a single section. I am able to access the objects using [[fetchedResultsController fetchedObjects] objectAtIndex:index] for all of the objects. But it is failing when I use objectAtIndexPath like this: [fetchedResultsController objectAtIndexpath:indexPath]
An error occurs after I insert an row (for one of the SearchResult objects) into the corresponding table. The object appears to be inserted into the new table correctly. After I have visually confirmed this, I select one of the rows, and then the fun begins.
Here is the code where the error is occurring:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SearchResult *event;
NSLog(#"Number of sections in fetchedResultsController: %d", [[fetchedResultsController sections] count]);
for (NSUInteger i=0; i<[[fetchedResultsController fetchedObjects] count]; i++) {
event = (SearchResult*)[[fetchedResultsController fetchedObjects] objectAtIndex:i];
NSLog(#"object at index[%d]: %#", i, event.title );
NSLog(#"indexPath for object at index[%d]:%#", i, [fetchedResultsController indexPathForObject:event]);
}
NSLog(#"indexPath passed to method: %#", indexPath);
SearchResult *result = [fetchedResultsController objectAtIndexPath:indexPath]; // *** here is where the error occurs ***
[viewDelegate getDetails:result];
}
I am having a failure in the last line. The log looks like this:
Number of sections in fetchedResultsController: 1
object at index[0]: Cirles
indexPath for object at index[0]:(null)
object at index[1]: Begin
indexPath for object at index[1]:(null)
object at index[2]: Copy
indexPath for object at index[2]:(null)
object at index[3]: Unbound
indexPath for object at index[3]:(null)
indexPath passed to method: <NSIndexPath 0x64ddea0> 2 indexes [0, 2]
After executing the NSLog statements, I get an exception at the last line using [fetchedResultsController objectAtIndexPath:indexPath]. It happens for other index values too, but they always look valid.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 2 in section at index 0'
So, to summarize, there appear to be the right number of fetched objects, there is one section (0), I can access each one by one method, but not by the other. The indexPathForObject: is always returning (null).
Is this a bug or am I misunderstanding something?
UPDATE
Here is the code, implementing the NSFetchedResultsControllerDelegate protocol methods.
- (void) controllerWillChangeContent:(NSFetchedResultsController *)controller {
NSLog(#"Favorites controllerWillChangeContent");
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
NSLog(#"Favorites Table controllerDidChangeContent");
[self.tableView endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
NSLog(#"Favorites Changed Object");
switch (type) {
case NSFetchedResultsChangeInsert:
NSLog(#"--- Favorite was inserted");
if ([[self.fetchedResultsController fetchedObjects] count] == 1) {
// configure first cell to replace the empty list indicator by first deleting the "empty" row
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
}
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeDelete:
NSLog(#"--- Favorite was deleted");
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
if ([[self.fetchedResultsController fetchedObjects] count] == 0) {
// configure first cell to show that we have an empty list
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
break;
case NSFetchedResultsChangeMove:
NSLog(#"--- Favorite was moved");
break;
case NSFetchedResultsChangeUpdate:
NSLog(#"--- Favorite was updated");
[self configureCell:(ResultCell*)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
default:
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
switch (type) {
case NSFetchedResultsChangeInsert:
NSLog(#"Favorites Section Added");
break;
case NSFetchedResultsChangeDelete:
NSLog(#"Favorites Section Deleted");
break;
default:
break;
}
}
UPDATE 2
I don't know if it matters, but the tableView is initialized with this line:
tableView = [[UITableView alloc] initWithFrame:CGRectMake(...) style:UITableViewStyleGrouped];
UPDATE 3
When I change the offending line as follows, it works fine. But I would rather keep the indexing the same as it is with the table.
//SearchResult *result = [self.fetchedResultsController objectAtIndexPath:indexPath];
SearchResult *result = [[self.fetchedResultsController fetchedObjects] objectAtIndex:[indexPath indexAtPosition:1]]
I had the same problem now.
For me it helped to initialise NSFetchedResultsController with cacheName: nil.

Crash on UITableView endUpdates when moving last row in section

I have an UITableViewController which is backed by an NSFetchedResultsController.
My NSFetchedResultsController put results into two sections based on a boolean.
In a background thread, the datasource is altered such that rows are added or removed.
I have my background thread's NSManagedObjectContext's merging correctly and this situation work fine for most cases. Rows alter when the data is changed and move between sections with animations.
There is one situation however where my application crashes with an EXC_BAD_ACCESS.
The case is when the last row in a section is moved to the other section. (Stack trace below). The crash occurs in objc_msgSend but the normal debugging tips I use aren't returning anything helpful, I simply receive "Value can't be converted to integer" from gdb.
The NSFetchedResultsControllerDelegate methods get called in the following order:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
// ^ The parameters specify a deletion of the 1st section
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath;
// ^ The parameters specify a moved row from the 1st section to the 1st section
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
Looking through StackOverflow, many of the people who encounter similar issues are backing their UITableViewController manually using an NSArray or NSMutableArray and they're simply not updating the datasource at the correct time. In this case the NSFetchedResultsController is handling returning the number of rows and sections and is definitely updated by the time the [tableView endUpdates] is called.
Has anyone got any debugging tips, pointers or solutions?
This blog post hints at working around a similar problem where a new section is created.
I have two options if I cannot solve this issue as is:
Forgo animation on rows and simply call [tableView reloadData]
Switch to using NSArray backed storage for the UITableView and hope for the best
Update
I have modified the Apple sample Core Data application "Locations" to use an NSFetchedResultsController directly and reproduced the issue.
I have uploaded the source code for this project. Use that project to reproduce the issue.
Simply click the + button a few times and wait
Every 5 seconds an item gets moved to another category.
When the first category is about to be emptied, it crashes.
The crash is sometimes about creating two animations for the same cell as per other StackOverflow questions on the topic. More commonly the crash is as discussed above and below.
Reference
The stack trace:
#0 0x31e0afbc in objc_msgSend ()
#1 0x32c11522 in -[_UITableViewUpdateSupport(Private) _computeRowUpdates] ()
#2 0x32c10510 in -[_UITableViewUpdateSupport initWithTableView:updateItems:oldRowData:newRowData:oldRowRange:newRowRange:context:] ()
#3 0x32c0f99e in -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] ()
#4 0x32c0e66c in -[UITableView endUpdatesWithContext:] ()
#5 0x000088d6 in -[DraftHistoryController controllerDidChangeContent:] (self=0x3f6f1e0, _cmd=0x377ca29c, controller=0x3f716e0) at /Users/ataylor/Documents/Documents/Programming/iPhone/Drafter/Drafter/Drafter/DraftHistoryController.m:323
#6 0x3775a892 in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()
My NSFetchedResultsControllerDelegate methods are the same ones from the Apple sample code:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates]; // <---- Crash occurs here
}
The relevant UITableViewControllerDelegate methods are as follows:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
I took a quick look. Your modified Locations project didn't crash on me but it did generate core data exceptions. The problem lies in reloading the sections in controller:didChangeObject:. I changed the code as follows and everything looks good to me (including the section titles) on both iOS 4.3 and iOS 5.
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
// other cases here
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
I was super curious about the code that fixed an issue with adding a new section so I decided to pop it in place of my current NSFetchedResultsControllerDelegate methods and it indeed solved my problem. Tracing through, I have discovered that this code from the Apple "Locations" sample crashes when using the NSFetchedResultsController:
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
But the following code works perfectly:
case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationTop];
}
else {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
}
break;
I don't have a case where section headings will change, I won't continue debugging to solve the issue Apple's code purports to fix around section titles.
Because every question needs a swift answer, here's one (based on XJones's accepted answer). As is the case with the other answers, the magic is in the .Move: delete and insert instead of moving the row.
This is my NSFRC delegate didChangeObject:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Delete:
if let deletePath = indexPath {
self.tableView.deleteRowsAtIndexPaths([deletePath], withRowAnimation: .None)
}
break
case .Insert:
if let insertPath = newIndexPath {
self.tableView.insertRowsAtIndexPaths([insertPath], withRowAnimation: .Fade)
}
break
case .Update:
if let updatePath = indexPath {
self.tableView.reloadRowsAtIndexPaths([updatePath], withRowAnimation: .None)
}
break
case .Move:
if let indexPath = indexPath, newIndexPath = newIndexPath {
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
break
}
}
I was getting a crash overtime I deleted the last row in a section.
Do make sure you have implemented controller:didChangeSection:atIndex:forChangeType: to handle section removal.
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.viewTable insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.viewTable deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}