I've been trying to get a grip on CoreData. After several attempts to find a good tutorial on it I made a lot of progress with a tutorial on youtube. However when I got to the NSFetchedResultsController I'm getting a warning on my custom table cell ITDContactCell *cell = [tableView cellForRowAtIndexPath:indexPath] stating that it's an incompatible pointer initializing with UITableViewCell (the tutorial does not use custom cells).
Even worse, when I attempt to add an item I get this nice ugly error.
2013-12-04 19:15:48.407 Ping[490:60b] * Assertion failure in
-[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2903.23/UITableView.m:1138 2013-12-04
19:15:48.409 Ping[490:60b] CoreData: error: Serious application error.
An exception was caught from the delegate of
NSFetchedResultsController during a call to
-controllerDidChangeContent:. attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update with userInfo
(null)
I've spent hours trying to figure out what I'm doing wrong but I can't wrap my head around it. So if anyone can take a gander at my code and give me suggestions on how I can fix it I'd greatly appreciate it. Thank you.
Controller:
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.ContactsListTableView;
switch (type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
Contact *changeContact = [self.fetchedResultsController objectAtIndexPath:indexPath];
ITDContactCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.name.text = [NSString stringWithFormat:#"%# %#", changeContact.contactFirstName, changeContact.contactLastName];
}
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObjects:newIndexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
EDIT: My implementation of number of rows and sections.
- (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];
}
fetchedResultsController is defined in the interface simply as #property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController
The warning is referring to the fact that the method
[tableView cellForRowAtIndexPath:indexPath];
returns a UITableViewCell, which is not literally your ITDContactCell. Placing a cast before the method should remove the warning.
ITDContactCell *cell = (ITDContactCell*) [tableView cellForRowAtIndexPath:indexPath];
The other error is probably because of some lack of relationship between your fetchedResultsController and your tableView. Showing your tableView delegate and source methods would help. I assume you have already implemented something similar:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSError *error = nil;
if(![self.fetchedResultsController performFetch:&error]) {
// NSLog(#"Error! %#", error);
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return _fetchedResultsController.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> secInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [secInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
ITDContactCell *cell = (ITDContactCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// assign cell content
return cell;
}
-(NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
-(NSFetchedResultsController *) fetchedResultsController {
if(_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityName"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.ContactsListTableView;
switch (type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
Contact *changeContact = [self.fetchedResultsController objectAtIndexPath:indexPath];
ITDContactCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.name.text = [NSString stringWithFormat:#"%# %#", changeContact.contactFirstName, changeContact.contactLastName];
}
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObjects:newIndexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
EDIT 1:
As a note, you should not be using the reloadData method in your tableview, since the table is reloaded (via the fetched results controller) after saving changes to your NSManagedObjectContext.
EDIT 2:
Is you NSManagedObjectContext nil?
Related
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];
}
This post saved me
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."
I have a tableviewcontroller and a fetchresultscontroller which fills the table with objects values. I am trying then to delete an object from the core data model as well as the row from the table through commitEditingStyle, but I get this error. I am aware of the error meaning, but I can't figure out what I need to fix in my code to make it disappear. Any help is appreciated.
Error:
EcoShoppingUI[1365:60b] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2935.137/UITableView.m:1368
2014-07-25 15:03:42.137 EcoShoppingUI[1365:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
favTable.m
#import "favTable.h"
#import "ecoAppDelegate.h"
#import "favCell.h"
#import "favButton.h"
#import "FavoritesInfo.h"
#interface favTable ()
#end
#implementation favTable
#synthesize tableView;
#synthesize managedObjectContext;
#synthesize fetchedResultsController;
- (NSFetchedResultsController *)fetchedResultsController {
if (!fetchedResultsController) {
self.managedObjectContext = ((ecoAppDelegate *) [UIApplication sharedApplication].delegate).managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FavoritesInfo" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
fetchedResultsController = theFetchedResultsController;
fetchedResultsController.delegate = self;
}
return fetchedResultsController;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Favorites";
self.navigationController.navigationBar.translucent = NO;
self.managedObjectContext = ((ecoAppDelegate *) [UIApplication sharedApplication].delegate).managedObjectContext;
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
- (void)viewDidUnload
{
[self setTableView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.fetchedResultsController = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
id sectionInfo =
[[fetchedResultsController sections] objectAtIndex:section];
// NSLog(#"%zd", [fetchedResultsController.fetchedObjects count]); // amount of fetched objects
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"favCell";
favCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[favCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
[self configureCell:cell atIndexPath:indexPath];
cell.cellmanu.text = #"hhjh";
cell.cellimage.image = [UIImage imageNamed:#"test1.jpg"];
return cell;
}
- (void)configureCell:(favCell *)cell
atIndexPath:(NSIndexPath *)indexPath
{
FavoritesInfo*favoritesInfo = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.cellname.text = favoritesInfo.name;
cell.cellscore.text = favoritesInfo.score;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
ecoAppDelegate *appDelegatee = (ecoAppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = ((ecoAppDelegate *) [UIApplication sharedApplication].delegate).managedObjectContext;
FavoritesInfo*favoritesInfo = [self.fetchedResultsController objectAtIndexPath:indexPath];
[tableView
deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.managedObjectContext deleteObject:favoritesInfo];
[appDelegatee saveContext]; // to save changes
NSError *error= nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self.tableView reloadData];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// FetchedResultsController update methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )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];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
#end
Change to:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
ecoAppDelegate *appDelegatee = (ecoAppDelegate *)[[UIApplication sharedApplication] delegate];
FavoritesInfo*favoritesInfo = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.managedObjectContext deleteObject:favoritesInfo];
[appDelegatee saveContext]; // to save changes
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
It's hard to know what some bits of your code are doing, seeing as you are referencing your app delegate. Let me know what this does. it should sort the issue but i'm not 100%.
Your section header column should be the first attribute in sort order.
Currently my table view loads a long list of items NSMutableArray *certificates;
I want to group these into sections based on the certificateType. So far I have grouped the sections but need add the data to the sections based on the certificateType
The UITableView is populated from core data object Certificate
Can I sort these into sections using the name of cell.textLabel.text = [NSString stringWithFormat:#"%# (%#)", thisCert.certificateType.title, thisCert.title];
Or should I split up NSMutableArray *certificates; into 4 separate arrays
Any advice on best way to approach this would be appreciated
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
LogCmd();
return 4;
}
//Section Titles
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
//Section Text
[[UILabel appearanceWhenContainedIn:[UITableViewHeaderFooterView class], nil] setFont:[UIFont systemFontOfSize:18]];
//Section Titles
NSArray *sectionTitles = [NSArray arrayWithObjects:#"BlueCert", #"GreenCert", #"RedCert", #"OrangeCert", nil];
NSString *result = [sectionTitles objectAtIndex:section];
return result;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 60.0f;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
return self.searchResults.count;
}
else {
return self.certificates.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
LogCmd();
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.textLabel.font = [UIFont boldSystemFontOfSize:14.0f];
cell.imageView.image = [UIImage imageNamed:#"IconFolderOpen"];
}
Certificate *thisCert;
if (tableView == self.searchDisplayController.searchResultsTableView) {
thisCert = (self.searchResults)[indexPath.row];
}
else {
thisCert = (self.certificates)[indexPath.row];
}
cell.textLabel.text = [NSString stringWithFormat:#"%# (%#)", thisCert.certificateType.title, thisCert.title];
cell.detailTextLabel.text = [NSString stringWithFormat:#"Reference: %#", thisCert.reference];
return cell;
}
If your data is dynamic, I suggest that you set up an NSFetchedResultsController.
That way your data is still together in the managed object context, but the data source for the table view can be broken up into sections based on title. Set the sectionNameKeyPath as certificate title for the NSFetchedResultsController. Set a sort descriptor based on certificate title for the NSFetchRequest used to get data into the NSFetchedResultsController.
Implement your view controller as the delegate for the NSFetchedResultsController.
Adding some high level code for elaboration. Most of this code is styled from the book Core Data Data Storage and Management for Ios, OS X, and Icloud by Marcus Zarra.
Assuming you have a managed object context and your are adding properties for NSFetchedResultsController and NSFetchRequest in your view controller
- (NSFetchedResultsController *) certificatesFetchedResultsController{
if(!_certificatesFetchedResultsController){
_certificatesFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:self.certificatesFetchRequest managedObjectContext:self.certitificatesMOC sectionNameKeyPath:#"title" cacheName:nil];
_certificatesFetchedResultsController.delegate = self;
}
return _certificatesFetchedResultsController;
}
- (NSFetchRequest *) certificatesFetchRequest{
if(!_certificatesFetchRequest){
_certificatesFetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Certificate"];
NSSortDescriptor *sortTitle = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:NO];
[_certificatesFetchRequest setSortDescriptors:#[sortTitle]];
}
return _certificatesFetchRequest;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller{
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[self.tableView endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type{
switch(type) {
case NSFetchedResultsChangeInsert:
[[self transactionsTblView] insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[self transactionsTblView] deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath{
NSArray *newArray = #[[NSIndexPath indexPathForItem:newIndexPath.row inSection:newIndexPath.section];
NSArray *oldArray = #[[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section];
switch(type) {
case NSFetchedResultsChangeInsert:
[self.transactionsTblView insertRowsAtIndexPaths:newArray withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.transactionsTblView deleteRowsAtIndexPaths:oldArray withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
NSManagedObject *object = [self.certificatesFetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
// Create your section title using data in the managed object and return the UIView
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *object = [self.certificatesFetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
// Configure cell data as per data in managed object
}
Somewhere, initialze the fetched result controller. Maybe in viewDidLoad
[self.certificatesFetchedResultsController performFetch:nil];
I have been trying for several days to transfer my data base information (core data) to a tableview using fecthedResultsController but nothing seams to work!
my entity name is "password" and ther are 4 strings attributes.
i'm trying to make a table that its sections are defined by an attribute called "type".
right now, that is how my code on my mainViewController.m looks like:
-(NSFetchedResultsController*)fecthedResultsController
{
if(fecthedResultsController!=nil)
return fecthedResultsController;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Password" inManagedObjectContext:[self manageObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"type" ascending:YES];
//here is the problam
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
fecthedResultsController=[[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[self manageObjectContext] sectionNameKeyPath:#"type" cacheName:nil];
fecthedResultsController.delegate=self;
NSLog(#"fecthedResultsController");
return fecthedResultsController;
}
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tblMain beginUpdates];
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tblMain endUpdates];
}
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView*tableview=self.tblMain;
NSLog(#"didChangeObject");
switch (type) {
case NSFetchedResultsChangeInsert:
[tableview insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:[tableview deleteRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:{
Password*p=[self.fecthedResultsController objectAtIndexPath:indexPath];
UITableViewCell*cell=[tableview cellForRowAtIndexPath:indexPath];
cell.textLabel.text=p.userName;
cell.detailTextLabel.text=p.password;
// cell.imageView.image=[UIImage imageWithData: p.photodata];
}
break;
case NSFetchedResultsChangeMove:
[tableview deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableview insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
default:
break; }
}
-(void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger) sectionIndex forChangeType: (NSFetchedResultsChangeType)type
{
NSLog(#"didChangeSection");
switch (type) {
case NSFetchedResultsChangeInsert:
[self.tblMain insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:[self.tblMain deleteSections:[NSIndexSet
indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
default:
break;
}
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [[self.fecthedResultsController sections]count];
// NSLog(#"%i",[[self.fecthedResultsController sections]count]);
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id<NSFetchedResultsSectionInfo>secInfo=[[self.fecthedResultsController sections]objectAtIndex:section];
return [secInfo numberOfObjects];
NSLog(#"numberOfRowsInSection");
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
Password*p=[self.fecthedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text=p.userName;
// if(p.photodata!=nil)
// cell.imageView.image=[UIImage imageWithData: p.photodata];
return cell;
}
-(NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:
(NSInteger)section
{
return [[[self.fecthedResultsController sections]objectAtIndex:section]name];
NSLog(#"cellForRowAtIndexPath");
}
the error that the log gives me is:
2013-04-30 12:31:04.326 passwordCore[33586:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Password''
*** First throw call stack: (0x1fac012 0x13e9e7e 0x10eff57 0x2d18 0x3832 0x20383e 0x204554 0xba793 0xc9937 0xc92dc 0xccdd6 0xd1a7e 0x6e2dd 0x13fd6b0 0x25a8fc0 0x259d33c 0x259d150 0x251b0bc 0x251c227 0x25beb50 0x1c39f 0x1ce81 0x2dcb5 0x2ebeb 0x20698 0x1f07df9 0x1f07ad0 0x1f21bf5 0x1f21962 0x1f52bb6 0x1f51f44 0x1f51e1b 0x1c17a 0x1dffc 0x1d3d 0x1c65) libc++abi.dylib: terminate called throwing an exception (lldb)
and it points to the 6th line of my code.
i dont understand if the problam is the manageObjectContext or the entityForName:#"Password"
i was told that this is a esay way to deal with tableview and data..
Right now I'm just frustrated...
i will love for some help.
You should get hold of a valid, main thread based NSManagedObjectContext. in a default core data project (the one provided by the Apple template) the main managed object context is accessible via the AppDelegate.
You should either pass your context from view controller to view controller, or just ask for it from the AppDelegate like so:
self.managedObjectContext = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;
I have encountered behavior with the FRC and TableView delegates that appears to be inconsistent:
The initial call to performFetch causes the delegate methods to be called as expected. However, if I update the predicate on the FRC's baseFetch and then call performFetch again, the FRC delegate methods are never called, and the TableView is not updated with new data. To force the table to update and display the new data I am explicitly calling reloadData.
The initial call to performFetch correctly builds the TableView's sections. If I change FRCs (the new FCR has a different fetchRequest and sectionNameKeyPath) and call performFetch again, the table and sections update to match the new results, as expected. HOWEVER, if I then go back to the original FRC and call performFetch, the sections have the correct names but with the previous FRC's section/row structure. To force the sections to update I am explicitly calling reloadSectionIndexTitles.
My feeling is that the delegates should be firing anytime we call performFetch if something changes. Explicitly calling reloadData and reloadSectionIndexTitles seems like an expensive and unnecessary step since that's what the delegates exist for. Am I missing something?
Here's the relevant code:
NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.myTable beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.myTable insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.myTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[self.myTable cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[self.myTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[self.myTable 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.myTable insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.myTable 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.myTable endUpdates];
}
UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return [[currentFCR sections] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[currentFCR sections] objectAtIndex:section];
return section.name;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
id <NSFetchedResultsSectionInfo> sectionInfo = [[currentFCR sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
// Customize the appearance of table view cells.
- (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...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Fetching
- (NSFetchedResultsController *)FRC1 {
if (FRC1 != nil) {
return FRC1;
}
NSFetchRequest *request = [[DataProvider instance] getFetch];
[[DataProvider instance] sortListByFirstSort:request];
[request setFetchBatchSize:20];
FRC1 = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:[[DataProvider instance] managedObjectContext]
sectionNameKeyPath:#"groupName"
cacheName:nil];
return FRC1;
}
- (NSFetchedResultsController *)FRC2 {
if (FRC2 != nil) {
return FRC2;
}
NSFetchRequest *request = [[DataProvider instance] getFetch];
[[DataProvider instance] sortListBySecondSort:request];
[request setFetchBatchSize:20];
FRC2 = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:[[DataProvider instance] managedObjectContext]
sectionNameKeyPath:#"name"
cacheName:nil];
return FRC2;
}
-(void)doFetch{
NSError *error;
if (![currentFCR performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self.myTable reloadData];
[self.myTable reloadSectionIndexTitles];
}
NSFetchedResultsControllerDelegate methods controllerWillChangeContent:, etc, are only called if a delegate is set on an NSFetchedResultsController instance. I don't see any delegates set in your code.
The initial table load (or explicitly calling reloadTable) will cause the UITableViewDataSource methods to be called, fetching data from the currentFCR. The NSFetchedResultsControllerDelegate methods will not be called if a delegate is not set on an NSFetchedResultsController.
Also, NSFetchedResultsControllerDelegate methods are only called if any of the relevant managed objects change (or are added/deleted) within the NSManagedObjectContext.
If you want to replace the NSFetchedResultsController object you will need to reload the table explicitly.