Reputation: 1954
UPDATE: In the comments someone pointed out that I was unnecessarily dispatching to the main thread. After removing the dispatches and unnecessary begin/end updates
, now when I try to delete a cell, it calls didChangeObject
with case NSFetchedResultsChangeUpdate
(as opposed to NSFetchedResultsChangeDelete
), which calls configureCell
.
The line that crashes the program is CollectedLeaf* theCollectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];
in the below method. The crash log is, no section at index 20 in sections list
.
- (void)configureCell:(SpeciesCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
CollectedLeaf* theCollectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];
[cell setCollectedLeaf:theCollectedLeaf];
}
I am getting an Invalid update: invalid number of rows in section
error every time I swipe to delete a cell from my table. I get the breakpoint specifically at [_table endUpdates]
in controllerDidChangeContent
.
In many SO posts, the reason for this error is that the object had not been deleted from data source before deleting the row from the table. I delete the object from the data source in commitEditingStyle
, which is called before deleteRowsAtIndexPaths
in didChangeObject
.
The fact that I'm still getting the Invalid update
despite my order leads me to think that I am not properly/successfully deleting it from the data source. I am still new to iOS - how can I make sure an object is removed from my core data?
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[_table beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[_table beginUpdates];
[_table insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
[_table endUpdates];
break;
case NSFetchedResultsChangeDelete:
NSLog(@"delete called");
[_table deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
// [self configureCell:[_table cellForRowAtIndexPath:indexPath]
// atIndexPath:indexPath];
[_table reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
NSLog(@"fetched update");
break;
case NSFetchedResultsChangeMove:
[_table deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[_table 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:
[_table insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[_table deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[_table endUpdates];
}
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(_segmentedControl.selectedSegmentIndex == 1) {
NSLog(@"index path at editingStyleForRowAtIndexPath %@", indexPath);
return UITableViewCellEditingStyleDelete;
}
return NULL;
}
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
// [super tableView: tableView didEndEditingRowAtIndexPath:indexPath];
if(_segmentedControl.selectedSegmentIndex == 1) {
for (UITableViewCell *cell in [_table visibleCells]) {
for (UIView *subview in cell.contentView.subviews)
[subview.layer removeAllAnimations];
}
}
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
/*Only allow deletion for collection table */
if(_segmentedControl.selectedSegmentIndex == 1) {
if (editingStyle == UITableViewCellEditingStyleDelete)
{
NSLog(@"index path at commitediting style %@", indexPath);
CollectedLeaf* collectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];
LeafletPhotoUploader * leafletPhotoUploader = [[LeafletPhotoUploader alloc] init];
leafletPhotoUploader.collectedLeaf = collectedLeaf;
if([LeafletUserRegistration isUserRegistered]) {
[leafletPhotoUploader deleteCollectedLeaf:collectedLeaf delegate:self];
}
// Delete the managed object for the given index path
NSManagedObjectContext *context = [collectionFetchedResultsController managedObjectContext];
[context deleteObject:[collectionFetchedResultsController objectAtIndexPath:indexPath]];
// Save the context.
NSError *error;
if (![context save:&error])
{
NSLog(@"Failed to save to data store: %@", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0)
{
for(NSError* detailedError in detailedErrors)
{
NSLog(@" DetailedError: %@", [detailedError userInfo]);
}
}
else
{
NSLog(@" %@", [error userInfo]);
}
}
}
}
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"speciesCell";
SpeciesCell* speciesCell = (SpeciesCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:speciesCell atIndexPath:indexPath];
speciesCell.labelCheckMark.backgroundColor = [UIColor blackColor];
return speciesCell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
id <NSFetchedResultsSectionInfo> sectionInfo = [[collectionFetchedResultsController sections] objectAtIndex:section];
if([sectionInfo numberOfObjects] == 0){
UILabel *noDataLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, tableView.bounds.size.height)];
noDataLabel.text = @"Press the Snap It! tab to start collecting!";
//Start your collection by tapping on the Snap It! tab.";
noDataLabel.textColor = [UIColor whiteColor];
noDataLabel.textAlignment = NSTextAlignmentCenter;
tableView.backgroundView = noDataLabel;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
return [sectionInfo numberOfObjects];
}
Upvotes: 2
Views: 142
Reputation: 1954
objectAtIndexPath
is pretty buggy and can only be called within a bounds check, so I now do all of my work with the core data object within the nest if statement
CollectedLeaf* theCollectedLeaf = nil;
if ([[collectionFetchedResultsController sections] count] > [indexPath section]){
id <NSFetchedResultsSectionInfo> sectionInfo = [[collectionFetchedResultsController sections] objectAtIndex:[indexPath section]];
if ([sectionInfo numberOfObjects] > [indexPath row]){
theCollectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];
//do whatever else I need, delete the object, etc
}
}
As the linked post describes,
"The reason you are getting an exception is that the state of the NSFetchedResultsController is getting out of sync with the state of the tableview. When your crash happens the tableView just asked your delegate methods (numberOfSectionsInTableView and tableView:numberOfRowsInSection: for the number of rows and sections. When it asked that, the NSFetchedResultsController had data in it, and gave positive values (1 section, 3 rows). But in-between that happening and your crash, the entities represented by the NSFetchedResultsController were deleted from the NSManagedObjectContext. Now cellForRowAtIndexPath: is being called with an outdated indexPath parameter."
Upvotes: 1