Getting An Assertion Failure Error - iphone

I am getting the following error when loading one of my views that has a UITableView in it. Does anyone know how to fix it? I have already tried deleting the (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath method, but that didn't help.
I'm thinking it has to do with the table's delegate not being updated properly with numberOfRowsInSection or something.
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-1448.89/UITableView.m:995
2011-06-05 00:38:12.116 App[14523:707] 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 (5) 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, 3 deleted). with userInfo (null)
Here is my code:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
NSLog(#"fetched results : \n%#\n",[self.fetchedResultsController fetchedObjects]);
// Commit the change.
NSError *error = nil;
// Update the array and table view.
if (![managedObjectContext save:&error])
{
// Handle the error.
}
//[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Set" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"sets == %#", self.exercise]];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog(#"fetched results : \n%#\n",[self.fetchedResultsController fetchedObjects]);
NSLog(#"fetch count: %i", [fetchedResultsController.fetchedObjects count]);
return self.fetchedResultsController;
}
#pragma mark - Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.setsTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.setsTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.setsTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.setsTableView;
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];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.setsTableView endUpdates];
[self.setsTableView reloadData];
}

is Try uncommenting [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
Please note:
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
should return a value 1 less than that of before calling 'deleteRows..'
Basic problem is this: You are instructing UI to delete one of the rows, but not removing that row from the back end.

If you are using the code from CoreDataBooks sample you might want to change this:
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
}
with
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
});
}
This will prevent NSFetchedResultsController from refreshing while the tableView is not visible because of the UINavigationController transition.
Of course this thing occurs only in simulator, because on the device the insert will take longer than the [self dismissModalViewControllerAnimated:YES] animation.

In my case when I called [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES]; only, I got this type of error. I solved this by removing the object from Array (from this array I am displaying data.) and called [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];

I put each section elements in separated arrays. Then put them into another array( arrayWithArray). My solution here for this problem:
[quarantineMessages removeObject : message];
[_tableView beginUpdates];
if([[arrayWithArray objectAtIndex: indPath.section] count] > 1)
{
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indPath] withRowAnimation:UITableViewRowAnimationBottom];
}
else
{
[_tableView deleteSections:[NSIndexSet indexSetWithIndex:indPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
[_tableView endUpdates];

Related

Core Data Insertion Error

I have a to-many relationship with Meals and Food (i.e. Meal <<------>> Food). Food is an abstracted class, and can be either a Fruit or Vegetable. I have a RootViewController that displays all the Food in a given Meal. In one class I add a Vegetable and another I add a Fruit.
I get the following error when I start adding these to a Meal. I am really not sure what is going on and how this is happening.
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046
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 (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 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)
2012-03-19 22:52:06.652 iGlucoTouch[682:11903] Meal Name: Meal #1 atIndex: 0
RootViewController.h
#interface RootViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate>
{
NSFetchedResultsController *_fetchedResultsController;
NSManagedObjectContext *_context;
UITableView *_tableView;
}
#property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, retain) NSManagedObjectContext *context;
#property (nonatomic, retain) UITableView *tableView;
#end
RootViewController.m
#implementation RootViewController
#synthesize fetchedResultsController = _fetchedResultsController;
#synthesize context = _context;
#synthesize tableView = _tableView;
- (void)viewDidAppear:(BOOL)animated
{
self.tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 400) style:UITableViewStyleGrouped];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.rowHeight = 60.0;
[self.view addSubview:self.tableView];
[self.tableView release];
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
self.context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
Food *food = [_fetchedResultsController objectAtIndexPath:indexPath];
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.meal removeFoodsObject:food];
NSError *error;
if (![self.context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
}
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
self.context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
Food *food = [_fetchedResultsController objectAtIndexPath:indexPath];
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.meal removeFoodsObject:food];
NSError *error;
if (![self.context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
}
}
- (NSFetchedResultsController *)fetchedResultsController
{
self.context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Food" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSPredicate *foodPredicate = [NSPredicate predicateWithFormat:#"ANY meals == %#", self.meal];
[fetchRequest setPredicate:foodPredicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
[sort release];
[fetchRequest release];
[theFetchedResultsController release];
return _fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (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:[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;
}
}
- (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
{
[self.tableView endUpdates];
}
I add a Fruit or Vegetable like this in another class:
self.context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
Fruit *fruit = [_fetchedResultsController objectAtIndexPath:indexPath];
[self.meal addFoodsObject:fruit];
NSError *error;
if (![self.context save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}
Apparently I was not setting the fetchedResultsController and the context to nil. I was in the dealloc, but that was not getting called because this was one of the views in a tab bar which was declared at the application delegate. So I simply added the code below. Maybe it is because I am sharing a context.
- (void)viewDidDisappear:(BOOL)animated
{
self.fetchedResultsController = nil;
self.context = nil;
}
Yes - sounds like you didn't add the new entry to the NSArray that the NSFetchResultsController is backed by. Right after you do [self.meal addFoodsObject:fruit] you should also be able to call something like
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight];
yourself. Jeff LaMarche's link is a great explanation.

How can I update my fetchedResultsController upon tableView row selection?

Ok, I am getting better at this Core Data stuff, but I've got a ways to go. This is how I am populating my fetchedResultsController when my view loads:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Visit" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Queue"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"(isActive == YES)"];
[fetchedResultsController.fetchRequest setPredicate:predicate];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
This works great. I have the fetchedResultsController hooked up to my tableView and I have 5 managed objects that are showing up. The problem I am running into is when I need to make a change to one of the managed objects.
As you can see in the predicate I am specifying that I only want managed objects that have isActive == YES. I need to change a managed object's isActive status to NO, and then remove it from the fetchedResultsController, and ultimately the tableView.
Here is how I am trying to do that:
-(void) seatedButton{
Visit * vis = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:reloadIndex inSection:0]];
vis.isActive = [NSNumber numberWithBool:NO];
NSError *error = nil;
if (![self.managedObjectContext 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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[tableView reloadData];
}
It appears that this would work, but it doesn't. When the tableView is reloaded, 5 objects come back as fitting the requirements of the predicate, when it should only be 4!
What am I doing wrong here? What do I need to do to update the fetchedResultsController? Thanks!
There are delegate methods to the fetchedResultsController you may want to use that should do this all for you automatically. Once you save the context, it update the table for you, so you won't have to call [tableView reloadData]
- (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 *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];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
EDIT: If you do this, you will need to implement the following method:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// configure cell;
}
I'm pretty sure adding the following code into your seatedButton method, just before the line with [tableView reloadData] will fix the problem, but not sure according to the doc that it should be necessary.
if (![self.fetchedResultsController performFetch:&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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}

NSFetchResultsController object refresing

I have 2 views. One display Shopping Lists from Core Data using NSFetchResultsController. The second one displays items on this lists using simple NSFetchRequests. Both views contain UITableView.
When I start app I create NSFetchResultsController (subclassed by ActiveFetchResults)
-(id)initActiveFetch{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Lista" inManagedObjectContext:[CoreDataHandler context]];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"archive == 0 AND deleted == NO"];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"position" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
if (self=[[ActiveFetchResults alloc]
initWithFetchRequest:request
managedObjectContext:[CoreDataHandler context]
sectionNameKeyPath:nil
cacheName:nil])
{
self.delegate = self;
}
[request release];
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self sections] count];
}
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (NSInteger)numberOfRowsInSection{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self sections] objectAtIndex:0];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ActiveListsCell* cell;
cell = (ActiveListsCell*)[tableView dequeueReusableCellWithIdentifier:#"ActiveLists"];
if (cell == nil) {
cell = [[[ActiveListsCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"ActiveLists"] autorelease];
}
NSManagedObject *managedObject = [self objectAtIndexPath:indexPath];
[cell setLista: (Lista*)managedObject];
NSLog(#"%#, %#, %d ",managedObject, [managedObject name], [[managedObject items] count]);
cell.activeListsDelegate = self;
[cell.roundedView setAlpha:1.0];
cell.button.alpha = 1.0;
cell.counter.alpha = 1.0;
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self sections] objectAtIndex:section];
return [sectionInfo name];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [self sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [self sectionForSectionIndexTitle:title atIndex:index];
}
- (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;
}
}
//-(BOOL)performFetch:(NSError **)error{
// ActiveFetchResults* ss = self;
// self = [self initActiveFetch];
//
// self.listView = ss.listView;
// self.listView.activeFetch = self;
// [self.listView.tableView setDelegate:self];
// [self.listView.tableView setDataSource:self];
//
// [ss release];
// return [super performFetch:error];
//}
- (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:
if ([anObject isKindOfClass:[Lista class]]) {
Lista* lista = (Lista*)anObject;
if ([[lista deleted] boolValue]) {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationLeft];
}
}
else
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
-(void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath{
ActiveListsCell* activeCell = (ActiveListsCell*)cell;
NSManagedObject *managedObject = [self objectAtIndexPath:indexPath];
[activeCell setLista: (Lista*)managedObject];
}
When I go to second View and add new Item for selected list by adding Item object to Core data and I return to List View it still shows old number of objects. And the items property of list which should show me all Items objects for this list is an NSarray object with items count not including new added objects. But second View shows me all items with those new added too.
In few words it looks like by using NSFetchResultsController I have all object frozen from the first fetch and not responding to changes despite using "performFetch:" function. While using simple NSFetchRequest in second View everything works fine.
Can someone tell me why NSFetchResultsController objects stay frozen and do not change while core data records change?
Possible issues:
Did you save NSManagedObjectContext after you updated or added objects?
Try adding this case
case NSFetchedResultsChangeUpdate:
[self.tableView reloadData];
break;
in your - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type method
try reloading data of tableview every time after you perform fetch:
[self.tableView reloadData];
If it still doesn't work, review the core data recipes sample code of Apple: http://developer.apple.com/library/ios/#samplecode/iPhoneCoreDataRecipes/Introduction/Intro.html

Why Can't I delete the bottom row of my UITableView?

When the user presses Edit, my UITableView adds an insert row at the top (with a green plus), and puts all the other rows into delete mode (red minus). Alternatively, the user can swipe-to-delete without pressing the edit button. I am using a couple of Ivars to keep track of whether the table is in edit mode from a swipe, or from pressing the edit button, and act accordingly (e.g. updating numberOfRowsInTableView: with the extra insert row when Edit has been pressed).
Everything works perfectly except on thing: when in Edit mode (i.e. the user has explicitly hit the edit button, and the insert row has appeared at the top), if the user tries to delete the bottom row, the next row up gets deleted instead. Deleting any other row is fine.
EDIT -- It appears to delete the row above, but if I immediately quit and reload the app, it turns out the bottom row has gone after all. So I'm guessing my UITableView is going out of sync with my NSFetchedResultsController somewhere.
Here's the code I'm using:
#import "ChecklistsViewController.h"
#import "Checklist.h"
#interface ChecklistsViewController (private)
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
- (void)addingView;
#end
#implementation ChecklistsViewController
#synthesize category, managedObjectContext, fetchedResultsController;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
editingFromSwipe = NO;
tableIsEditing = NO;
}
return self;
}
- (void)dealloc
{
[category release];
[managedObjectContext release];
[fetchedResultsController release];
[super dealloc];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
editingFromSwipe = NO;
tableIsEditing = NO;
self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.tableView.allowsSelectionDuringEditing = YES;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
int rows = [sectionInfo numberOfObjects];
if (self.editing) {
if (!editingFromSwipe && tableIsEditing) {
return rows +1;
}
return rows;
}
tableIsEditing = NO;
return rows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSLog(#"Should go into if statement here! \n");
if (tableView.editing) { //
if ((indexPath.row == 0) && (!editingFromSwipe)) {
NSLog(#"Configuring Add Button Cell while editing \n");
cell.textLabel.text = #"Add New Checklist";
cell.detailTextLabel.text = nil;
}
else {
NSLog(#"Configuring other cells while editing \n");
[self configureCell:cell atIndexPath:indexPath];
}
}
else {
NSLog(#"Configuring Cell Normally While Not Editing \n");
[self configureCell:cell atIndexPath:indexPath];
}
return cell;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
int numberOfRows = [self tableView:tableView numberOfRowsInSection:indexPath.section];
int rowBeingDeleted = indexPath.row +1;
if (tableIsEditing && !editingFromSwipe && numberOfRows == rowBeingDeleted) {
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]]];
}
else {
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
}
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
// TO DO: Fix error code.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
[self addingView];
}
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
int row = indexPath.row;
if (self.editing && row == 0) {
if (!editingFromSwipe && tableIsEditing) {
return UITableViewCellEditingStyleInsert;
}
else if (editingFromSwipe) {
return UITableViewCellEditingStyleDelete;
}
}
return UITableViewCellEditingStyleDelete;
}
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
editingFromSwipe = YES;
[super tableView:tableView willBeginEditingRowAtIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
[super tableView:tableView didEndEditingRowAtIndexPath:indexPath];
editingFromSwipe = NO;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
NSArray *addRow = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], nil];
[self.tableView beginUpdates];
if (!editingFromSwipe) {
if (editing) {
tableIsEditing = YES;
[self.tableView insertRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
else {
[self.tableView deleteRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
}
[self.tableView endUpdates];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row != 0) {
TO DO: Code for when row is selected
}
}
#pragma mark - Data
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Checklist *aChecklist = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = aChecklist.name;
cell.detailTextLabel.text = aChecklist.category.name;
}
- (void) addingView// :(id)sender
{
AddingViewController *viewController = [[AddingViewController alloc] initWithNibName:#"AddingViewController" bundle:nil];
viewController.delegate = self;
viewController.title = #"Add Checklist";
// Create the navigation controller and present it modally
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[self presentModalViewController:navigationController animated:YES];
viewController.textLabel.text = #"Enter new checklist name";
[navigationController release];
[viewController release];
}
#pragma mark - AddingViewDelegate
- (void)addingViewController:(AddingViewController *)addingViewController didAdd:(NSString *)itemAdded
{
if (itemAdded != nil) {
// Turn off editing mode.
if (self.editing) [self.navigationController setEditing:NO animated:NO];
// Add the category name to our model and table view.
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Checklist *newChecklist = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[category addChecklistsObject:newChecklist];
newChecklist.name = itemAdded;
// [newChecklist setDateStamp:[NSDate date]];
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
TO DO: fix error code.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Checklist" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set 4* the predicate so we only see checklists for this category.
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:#"category.name = %#", self.category.name];
[fetchRequest setPredicate:requestPredicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
// TO DO: error stuff
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchedResultsController;
}
#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 *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];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
#end
You can add static cells to UITableViews that get their data from a NSFetchedResultsController. But to do this you have to adjust almost all NSIndexPaths that are used within one of the UITableViewDelegate, UITableViewDataSource or NSFetchedResultsControllerDelegate methods.
I added some helper methods that translate the indexpath of the tableview to the indexpath of the fetched resultscontroller and the other way around. Something like this could be used if you want to add a row on top:
- (NSIndexPath *)tableIndexPathFromNSFRCIndexPath:(NSIndexPath *)ip {
if (editingMode && ip.section == 0) {
NSIndexPath *newIP = [NSIndexPath indexPathForRow:ip.row+1 inSection:ip.section];
return newIP;
}
return ip;
}
- (NSIndexPath *)nsfrcIndexPathFromTableIndexPath:(NSIndexPath *)ip {
if (editingMode && ip.section == 0) {
NSIndexPath *newIP = [NSIndexPath indexPathForRow:ip.row-1 inSection:ip.section];
return newIP;
}
return ip;
}
and then you have to change every method that passes an indexpath from the table to the fetchedresultscontroller or from the frc to the table. I show you two as an example.
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
newIndexPath = [self tableIndexPathFromNSFRCIndexPath:newIndexPath];
indexPath = [self tableIndexPathFromNSFRCIndexPath:indexPath];
switch(type) {
case NSFetchedResultsChangeInsert:
[self.listTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.listTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[self.listTableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[self.listTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.listTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[aTableView deselectRowAtIndexPath:indexPath animated:YES];
if (editingMode && indexPath.section == 0 && indexPath.row == 0) {
// Add New entry...
}
else {
indexPath = [self nsfrcIndexPathFromTableIndexPath:indexPath];
NSManagedObject *selectedObject = [self.fetchedResultsController objectAtIndexPath:indexPath]);
}
}
Your design it at fault. You shouldn't be adding rows anywhere but where they logically go in the table.
The entire point of a fetched results controller (FRC) is to synchronize the tableview with the data. The order of the rows in the table should reflect the order of managed objects in the fetchedObjects array. By inserting a row at the bottom or top and while adding an object that does not necessarily logically belong at the top or bottom of the table, are breaking that synchronization.
When you add a new managed object in addingViewController:didAdd: the FRC alerts it delegate which tries to redraw the table. You've tried to compensate for this but you really can't. All you indexes are coming off.
Instead of using a row to input new rows. Use a tableview header or footer view. That way, you can freeze the tableview, create the new object, then update the table and the new object will show up where it logical belongs in the table.
To not get the rows mixed up I would suggest putting the insert row in its own section. Since you're obviously just using one section you know that the section you send to FRC should always be 0. The code would be as simple as:
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]];
TechZen's solution will also work, so which solution you choose is entirely about what design you prefer. TechZen's solution doesn't interfere with having multiple sections, but this solution could be modified to support multiple sections as well.

Swipe-To-Delete triggers the insert row on my table

In my app, the user is presented with a table of data. When they click "edit", an edit row appears at the top with a green plus-sign - they can use this to add another item to the list.
This works fine when the user just taps the edit button, but if the users uses swipe-to-delete, the edit row appears (without the green plus) and everything goes weird (delete button appearing on the wrong row, etc).
Can anyone tell me what I'm doing wrong? Sorry just to dump a load of code but I've gone round in so many circles that I've lost all perspective!
#import "ChecklistsViewController.h"
#import "Checklist.h"
#interface ChecklistsViewController (private)
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
- (void)addingView;
#end
#implementation ChecklistsViewController
#synthesize category, managedObjectContext, fetchedResultsController;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
editControlsDidShow = NO;
}
return self;
}
- (void)dealloc
{
[category release];
[managedObjectContext release];
[fetchedResultsController release];
[super dealloc];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.tableView.allowsSelectionDuringEditing = YES;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
if (tableView.editing) return [sectionInfo numberOfObjects] +1;
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[self configureCell:cell atIndexPath:indexPath];
if (tableView.editing) {
if (indexPath.row == 0) {
cell.textLabel.text = #"Add New Checklist";
cell.detailTextLabel.text = nil;
}
if (indexPath.row != 0) {
[self configureCell:cell atIndexPath:indexPath];
}
}
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
NSError *error = nil;
if (![context save:&error])
{
// error code
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
[self addingView];
}
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
int row = indexPath.row;
if (self.editing && row == 0) {
if (editControlsDidShow) return UITableViewCellEditingStyleInsert;
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleDelete;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
editControlsDidShow = NO;
[super setEditing:editing animated:animated];
NSArray *addRow = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], nil];
[self.tableView beginUpdates];
if (editing) {
editControlsDidShow = YES;
[self.tableView insertRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
else {
[self.tableView deleteRowsAtIndexPaths:addRow withRowAnimation:UITableViewRowAnimationLeft];
}
[self.tableView endUpdates];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// not done yet
}
#pragma mark - Data
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Checklist *aChecklist = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [aChecklist.name description];
cell.detailTextLabel.text = [aChecklist.category.name description];
}
- (void) addingView// :(id)sender
{
//Create the root view controller for the navigation controller
AddingViewController *viewController = [[AddingViewController alloc] initWithNibName:#"AddingViewController" bundle:nil];
viewController.delegate = self;
viewController.title = #"Add Checklist";
// Create the navigation controller and present it modally
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[self presentModalViewController:navigationController animated:YES];
viewController.textLabel.text = #"Enter new checklist name";
[navigationController release];
[viewController release];
}
#pragma mark - AddingViewDelegate
- (void)addingViewController:(AddingViewController *)addingViewController didAdd:(NSString *)itemAdded
{
if (itemAdded != nil) {
// Turn off editing mode.
if (self.editing) [self.navigationController setEditing:NO animated:NO];
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Checklist *newChecklist = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[category addChecklistsObject:newChecklist];
newChecklist.name = itemAdded;
NSError *error = nil;
if (![context save:&error])
{
// error code
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Checklist" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set 4* the predicate so we only see checklists for this category.
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:#"category.name = %#", self.category.name];
[fetchRequest setPredicate:requestPredicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
// handle the error properly!
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchedResultsController;
}
#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 *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];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
#end
I found one thing that you probably want to change:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
I don't think you want the edit row to be editable. Here's code to fix that:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return (indexPath.row != 0);
}
You might want an if else to make it more readable, but if you're lazy this will suffice.
Also could you point out where you add the green plus-sign? I'll update my answer when you do.
EDIT: The problem is that when you use swipe-to-delete only the row you swipe enters edit mode. The edit row doesn't enter edit mode and therefore the green plus-sign isn't shown. If the delete button is appearing on the wrong row, I would assume that the problem is that when the table view enters edit mode the edit row is added, which updates the indexPaths of all consecutive rows.
As of right now I cannot provide any code for how to solve the problem, but I can provide you with an idea. You shouldn't add the edit row when the user uses swipe-to-delete. If you can identify when swipe-to-delete is used and not add the edit row based on that your problem should be solved. I've never had a similar problem, so I've never had to do this. Thus I can't provide the details on how to do this.
If you need more/better advise or help with the implementation, let me know.
I've had the same problem and I just fixed it with this trick:
You can check how editing mode was set to YES in this vein:
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
editingFromSwipe = YES;
[super tableView:tableView willBeginEditingRowAtIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
[super tableView:tableView didEndEditingRowAtIndexPath:indexPath];
editingFromSwipe = NO;
}
And I guess that you already know how you proceed from this point, you create an if-function inserting the add-row-row only when editingFromSwipe is zero.
I think that we all agree that Apple is not very cooperative here. I hope it helps!