I'm new to iOS development/CoreData and have searched for an answer to my specific problem, but haven't found one yet.
I have a TableView with some Objects from my database. Each of the objects has an attribute "name". If I open the details view of one of the objects and go back to the tableView with the "back" Button everything works fine. It also works if I do no changes to the "name" TextField and press the "done" Button to get back to my tableView. As soon as I do some changes in the "name" TextField I get this error:
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (9) must be equal to the number of rows contained in that section before the update (9), 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).
Why is there an object being deleted?
Here is the code of my DetailsStaticTableViewController.m:
#import "WeinStaticTableViewController.h"
#import "Wein.h"
#import "JSMToolBox.h"
#interface WeinStaticTableViewController ()
#end
#implementation WeinStaticTableViewController
#synthesize nameTextView;
#synthesize landTextView;
#synthesize herstellerTextView;
#synthesize wein = _wein;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.wein != nil) {
self.nameTextView.text = self.wein.name;
}
}
- (void) viewDidDisappear:(BOOL)animated
{
[[JSMCoreDataHelper managedObjectContext] rollback];
[super viewDidDisappear:animated];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[self setNameTextView:nil];
[self setLandTextView:nil];
[self setHerstellerTextView:nil];
self.wein = nil;
}
- (IBAction)barButtonItemDonePressed:(id)sender
{
NSLog(#"%s", __PRETTY_FUNCTION__);
if (self.wein != nil) {
self.wein.name = self.nameTextView.text;
[JSMCoreDataHelper saveManagedObjectContext:[JSMCoreDataHelper managedObjectContext]];
}
[self.navigationController popViewControllerAnimated:YES];
}
#end
Here is the code of my JSMCoreDataHelper.m:
#import "JSMCoreDataHelper.h"
#implementation JSMCoreDataHelper
+ (NSString*) directoryForDatabaseFilename
{
return [NSHomeDirectory() stringByAppendingString:#"/Library/Private Documents"];
}
+ (NSString*) databaseFileName
{
return #"database.sqlite";
}
+ (NSManagedObjectContext*) managedObjectContext
{
static NSManagedObjectContext *managedObjectContext;
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSError *error;
// Verzeichnis unter dem das Datenbank-File abgespeichert werden soll, wird neu erzeugt, falls es noch nicht existiert.
[[NSFileManager defaultManager] createDirectoryAtPath:[JSMCoreDataHelper directoryForDatabaseFilename] withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
NSLog(#"Fehler: %#", error.localizedDescription);
return nil;
}
NSString *path = [NSString stringWithFormat:#"%#/%#", [JSMCoreDataHelper directoryForDatabaseFilename], [JSMCoreDataHelper databaseFileName]];
NSURL *url = [NSURL fileURLWithPath:path];
NSManagedObjectModel *managedModel = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *storeCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedModel];
if (! [storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) {
NSLog(#"Fehler: %#", error.localizedDescription);
return nil;
}
managedObjectContext = [[NSManagedObjectContext alloc] init];
managedObjectContext.persistentStoreCoordinator = storeCoordinator;
return managedObjectContext;
}
+ (id) insertManagedObjectOfClass: (Class) aClass inManagedObjectContext: (NSManagedObjectContext*) managedObjectContext
{
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(aClass) inManagedObjectContext:managedObjectContext];
return managedObject;
}
+ (BOOL) saveManagedObjectContext: (NSManagedObjectContext*) managedObjectContext
{
NSError *error;
if (! [managedObjectContext save:&error]) {
NSLog(#"Fehler: %#", error.localizedDescription);
return NO;
}
return YES;
}
+ (NSArray*) fetchEntitiesForClass: (Class) aClass withPredicate: (NSPredicate*) predicate inManagedObjectContext: (NSManagedObjectContext*) managedObjectContext
{
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:NSStringFromClass(aClass) inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entityDescription;
fetchRequest.predicate = predicate;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(#"Fehler: %#", error.localizedDescription);
return nil;
}
return items;
}
+ (BOOL) performFetchOnFetchedResultsController: (NSFetchedResultsController*) fetchedResultsController
{
NSError *error;
if (! [fetchedResultsController performFetch:&error]) {
NSLog(#"Fehler: %#", error.localizedDescription);
return NO;
}
return YES;
}
#end
And finally the numberOfSections and numberOfRowsInSection of my MainTableViewController:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
Any clue could help...thank you !
Heres my code using a NSFetchedResultsController, this works fine with your code for managing CoreData.
Hope this helps.
#import "Person.h"
#import "mainViewController.h"
#import "JSMCoreDataHelper.h"
#import "staticInfoViewController.h"
#implementation mainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Load From CoreData
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
#pragma mark - TableView DataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Person *p = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:#"Name: %#", p.name];
}
#pragma mark - TableView Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
staticInfoViewController *info = [[staticInfoViewController alloc] initWithNibName:#"staticInfoViewController" bundle:[NSBundle mainBundle]];
info.currentPerson = [_fetchedResultsController objectAtIndexPath:indexPath];
[self.navigationController pushViewController:info animated:YES];
}
#pragma mark - Set NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Person" inManagedObjectContext:[JSMCoreDataHelper managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[JSMCoreDataHelper managedObjectContext] sectionNameKeyPath:nil
cacheName:#"cache"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
#pragma mark - NSFetchedResultsController Delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray
arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray
arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (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)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
#end
you have wrong number of data in UI and in core data. the row count dosnt match in UI and core data. you have daleted one row but UI/fetchresultcontroller does not know about and tries to read it from the database. either refetch after changes or implement the fetch result controller delegate:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
do not forget to set fetch result controller to self.( viel spass )
Related
I have a Single View Application. It uses a ViewController embedded in a NavigationController. I have one UITextField and a UITableView added to the ViewController.
My model is extremely simple. It has two entities:
itemname
createdAt
I am able to create a new item without issue. When I look at the SQLite database, it do see the actual data being inserted correctly. My issue right now is that I cannot seem to figure out why the data is not being displayed in my UITableView.
ViewController.m
#import "ViewController.h"
#interface ViewController () <NSFetchedResultsControllerDelegate>
#property (strong) NSMutableArray *items;
#property(nonatomic, strong) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"itemname" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"ItemCache"];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Item"];
self.items = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
NSManagedObject *item = [self.items objectAtIndex:indexPath.row];
[cell.textLabel setText:[NSString stringWithFormat:#"%#", [item valueForKey:#"itemname"]]];
[cell.detailTextLabel setText:[item valueForKey:#"itemname"]];
return cell;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObjectContext *context = [self managedObjectContext];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete object from database
[context deleteObject:[self.items objectAtIndex:indexPath.row]];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Delete! %# %#", error, [error localizedDescription]);
return;
}
// Remove device from table view
[self.items removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
- (IBAction)cancel:(id)sender {
//implement action here for cancel button to dismiss keyboard
}
- (IBAction)saveItem:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new managed object
NSManagedObject *newItem = [NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:context];
[newItem setValue:self.itemTextField.text forKey:#"itemname"];
NSLog(#"Creating new item");
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"updateItem"]) {
NSManagedObject *selectedItem = [self.items objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
ViewController *destViewController = segue.destinationViewController;
destViewController.itemTextField = selectedItem;
}
}
EDIT
Ive gone in and added my NSFetchedResultsController protocols.
- (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;
}
}
I seem to be getting an error on this line:
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
Does this need a datasource declared before tableView? If the UITableView is connected to the File Owner's datasource, should this be self?
I think it should actually look like this:
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
You need to implement NSFetchedResultsControllerDelegate to handle changes to results, such as when entities are inserted into the store.
The delegate will update the tableView's model.
At the least, you simply could reload the table when your delegate was called.
inside perform fetch block, implement [tableView reloadData];
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.
Just a caveat, this is my first ever question, be gentle with me.
I had a UITableViewController that conforms to the NSFetchedResultsControllerDelegate protocol. This all works perfectly. I want to change that to a UIViewController to be able to add more to the view than just the tableview. I created the tableView property and set the delegate and datasource. Everything works fine except that the FRC delegate methods are not called and the table is not updated. The inserts, edits, and deletes are being stored to the database, but the table is not updating when I change something in the context.
All the code is the basic template code for using a fetched results controller. When it sets the delegate in the line:
aFetchedResultsController.delegate = self;
It should be setting the delegate correctly, but it is not. Again, the only change I make is to remove the word Table from UITableViewController. It works with the UITableViewController but not the UIViewController. I have researched this extensively but have not found an answer.
Thanks.
Here's some code:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
.
.
.
#class NowWhatDetailViewController;
#class Event;
#interface NowWhatMasterViewController : UITableViewController <NSFetchedResultsControllerDelegate, ChangeDateViewControllerDelgate, EditEventViewControllerDelgate, PasswordViewControllerDelegate,
UITableViewDelegate, UITableViewDataSource> {
NSMutableArray *saveButtonItems;
IBOutlet UILabel *nextEventLabel;
IBOutlet UILabel *timeToNextEventLabel;
}
#property (strong, nonatomic) NowWhatDetailViewController *detailViewController;
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, weak) id <ChangeDateViewControllerDelgate> delegate;
#property (nonatomic, retain) IBOutlet UITableView *tableView;
.
.
.
#end
And the important sections of NowWhatMasterViewController.m
#import "NowWhatMasterViewController.h"
#interface NowWhatMasterViewController () {
}
#end
#implementation NowWhatMasterViewController {
Event *lastEditedEvent;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self performFetch];
[[self.splitViewController.viewControllers lastObject] topViewController];
self.detailViewController.viewNSDate = self.viewNSDate;
self.detailViewController.viewDate = self.viewDate;
self.detailViewController.viewSchedule = self.viewSchedule;
self.detailViewController.managedObjectContext = self.managedObjectContext;
self.detailViewController.fetchedResultsControllerDetail = self.fetchedResultsController;
[self.detailViewController updateDetailView];
// show the bottom toolbar
[self.navigationController setToolbarHidden:NO];
saveButtonItems = [self.toolbarItems mutableCopy];
nextEventLabel.text = #"next event";
timeToNextEventLabel.text = #"starts in ## minutes";
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
self.fetchedResultsController = nil;
}
- (void)viewDidUnload {
self.fetchedResultsController = nil;
}
#pragma mark - Table View
/*
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [dayEvents count];
}
*/
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
Event *event = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"EditEvent" sender:event];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
//return [dayEvents count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"EventCell"];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
if (_isLocked) {
return NO;
} else {
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]) {
// 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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// The table view should not be re-orderable.
return NO;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
Event *event = [self.fetchedResultsController objectAtIndexPath:indexPath];
[event toggleChecked];
[self configureCheckmarkForCell:cell withEvent:event];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// this updates the detail controller when a row is selected. Not sure we want to do that
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
self.detailViewController.detailItem = object;
}
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSLog(#"the fetched results controller is getting the events for %#", self.viewNSDate);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" 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:#"eventNSDate" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// setup the predicate to return just the wanted date and schedule
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(eventDate like '%#') AND (schedule.scheduleName like '%#')", [Event returnDateString:self.viewNSDate], self.viewSchedule.scheduleName]];
[fetchRequest setPredicate:requestPredicate];
// Clear out any previous cache
[NSFetchedResultsController deleteCacheWithName:#"Master"];
// 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:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// manually set the delegate?
return _fetchedResultsController;
}
- (void)performFetch
{
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
//FATAL_CORE_DATA_ERROR(error);
NSLog(#"database error");
return;
}
}
- (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:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
I want to delete record from my tableviewcontroller cell.
The code seems fine to me, the exception came when i press a row to delete...
this is my code of ViewController.h file
#import <UIKit/UIKit.h>
#interface IMSCategoryViewController : UITableViewController
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property(nonatomic,retain)NSArray *arr;
#property (readonly, strong, nonatomic) NSMutableArray *categoryArray;
#end
And This one in the implementation file.
#import "IMSCategoryViewController.h"
#import "IMSAppDelegate.h"
#import "Category.h"
#interface IMSCategoryViewController ()
{
NSManagedObjectContext *context;
}
#end
#implementation IMSCategoryViewController
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
#synthesize categoryArray;
#synthesize arr;
- (void)viewDidLoad
{
[super viewDidLoad];
// [self.tableView reloadData];
IMSAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
context = [appDelegate managedObjectContext];
NSEntityDescription *category = [NSEntityDescription entityForName:#"Category" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:category];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"is_active == 1"];
[request setPredicate:predicate];
[request setFetchBatchSize:25];
[request setEntity:category];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *srotDesc = [[NSArray alloc]initWithObjects:sort, nil];
[request setSortDescriptors:srotDesc];
NSError *error;
NSMutableArray *results = [[context executeFetchRequest:request error:&error] mutableCopy];
if (results == nil) {
//error handle here
}
[self setArr:results];
NSLog(#"there is category array");
[self.tableView reloadData];
[self.arr count];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.arr count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Category *category = [self.arr objectAtIndex:indexPath.row];
// Configure the cell...
cell.textLabel.text = [category name];
cell.detailTextLabel.text = [category descript];
return cell;
}
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
and where i am performing the delete action.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObject *recordtoDelete = [self.arr objectAtIndex:indexPath.row];
[_managedObjectContext deleteObject:recordtoDelete];
[self.tableView beginUpdates];
// Category *deleteRecord = [self.arr objectAtIndex:indexPath.row];
// [self.managedObjectContext deleteObject:deleteRecord];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
//handle error here
}
}
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
}
}
OKAY So when i run this code...
it gives this type of error...
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 (17) must be
equal to the number of rows contained in that section before the update (17),
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).
'
It's better to use NSFetchResultController than array, you can easily update tableView with NSFetchResultControllerDelgate. You only need 2 in a UItableViewController like this:
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
First Rewrite getter for NSFetchedResultController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Category" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"is_active == 1"];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:25];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[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:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
Rewrite datasource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Category *category = (Category *)[self.fetchedResultsController objectAtIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = [category name];
cell.detailTextLabel.text = [category descript];
}
Delete
- (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]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
- (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:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
I have a simple app that create some object. Everything works fine but if i press the add button that open a detailViewController and there i will press cancel instead of saving it goes back to de mainTableView but it show a blank row, if i open the sql file there's no empty object so it should be an issue between the fetchedResultController and tableView loading it.
i know its not the best way to add an Object because in my add action i create an entity even if the user wont save. Yet it shouldnt show the blank row. Why is that happening? How to fix?
This is my code:
IngredietnViewController.m
#import "IngredientsTableViewController.h"
#interface IngredientsTableViewController ()
#end
#implementation IngredientsTableViewController
#synthesize managedObjectContext = _managedObjectContext;
#synthesize fetchedResultsController = _fetchedResultsController;
#synthesize ingredient = _ingredient;
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Ingredients";
UIBarButtonItem *addButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addIngredient)];
self.navigationItem.rightBarButtonItem = addButton;
self.navigationItem.leftBarButtonItem = self.editButtonItem;
self.tableView.tableFooterView = [[UIView alloc] init];
}
#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][section];
NSLog(#"%lu", (unsigned long)[sectionInfo numberOfObjects]);
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];
}
// Configure the cell...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (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]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
#pragma mark - Action
-(void)addIngredient{
DetailIngredientViewController *detailIngredientVC =[[DetailIngredientViewController alloc]initWithNibName:#"DetailIngredientViewController" bundle:nil];
detailIngredientVC.delegate = self;
self.ingredient = [NSEntityDescription insertNewObjectForEntityForName:#"Ingredient" inManagedObjectContext:self.managedObjectContext];
detailIngredientVC.detailIngredient = self.ingredient;
[self.navigationController pushViewController:detailIngredientVC animated:YES];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{}
#pragma mark - Fetched Result
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Ingredient" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
- (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:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
#pragma mark - configureCell:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Ingredient *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = object.name ;
cell.detailTextLabel.text = object.rappresentation;
}
-(void)controller:(UIViewController *)controller didSaveIngredient:(Ingredient *)DetailIngredient{
NSLog(#"INGREDIENT SAVED");
NSError *error = nil;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error %# %#", error, [error userInfo]);
}
[self.tableView reloadData];
}
#end
DeataiIngredientlViewController.m
#implementation DetailIngredientViewController
#synthesize tfNameIngredient, detailIngredient, tvDescription;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:#selector(saveIngredient)];
self.navigationItem.rightBarButtonItem = saveButton;
}
-(void)saveIngredient{
if ([self verifyIngredient] == YES){
detailIngredient.name = tfNameIngredient.text;
detailIngredient.rappresentation = tvDescription.text;
[self.delegate controller:self didSaveIngredient:detailIngredient];
[self.navigationController popToRootViewControllerAnimated:YES];
}
else{
NSLog(#"SBALLATA");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"The Name and description are empty" delegate:self cancelButtonTitle:#"Back" otherButtonTitles:nil, nil];
[alert show];
}
}
-(BOOL)verifyIngredient{
if ([tfNameIngredient.text length] < 4 || [tvDescription.text length] < 4 ){
return NO;
}
else{
return YES;
}
}
#end
Your blank row corresponds to Ingredient NSManagedObject you created in addIngredient.
Even though you didn't save: the NSManagedObjectContext it still exists and is picked up by your NSFetchedResultsController.
When the user cancels the add you need to delete it. Perhaps add a controller:(UIViewController *)controller didCancelAddIngredient: method to your delegate?
ALSO, in controller:(UIViewController *)controller didSaveIngredient: you don't need to reloadData on your UITableView, that's what all that NSFetchedResultsControllerDelegate code is there to do for you.