TableView sections with NSManaged Object Context Throws Exception - objective-c

I recently migrated a project from a self-managed object using a config singleton to use the NSManaged Object Context with NSFetchedResultController. What I'm trying to do is fill a TableView with sections that are based on month however the user can select a cell and change the month. when that happens it causes the following exception thrown and the cells become unable to change or edit
[error] 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 1, but there are only 0 sections after the update with userInfo (null)
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 1, but there are only 0 sections after the update with userInfo (null)
Here is the main view controller fetch request:
- (NSFetchedResultsController<Budget *> *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
LogDebug(#"STARTED");
NSFetchRequest<Budget *> *fetchRequest = Budget.fetchRequest;
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"startTime" ascending:NO];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
NSFetchedResultsController<Budget *> *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"monthSection" cacheName:nil];
aFetchedResultsController.delegate = self;
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
LogError(#"Unresolved error %#, %#", error, error.userInfo);
abort();
}
_fetchedResultsController = aFetchedResultsController;
return _fetchedResultsController;
}
Then for the Tableview Sections Data Source:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
NSInteger count = [sectionInfo numberOfObjects];
LogDebug(#"Number of Rows: %ld in Section %ld",(long)count, (long)section);
return [sectionInfo numberOfObjects];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSInteger count = [[self.fetchedResultsController sections] count];
LogDebug(#"Sections: %ld",(long)count);
return [[self.fetchedResultsController sections] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
LogDebug(#"STARTED");
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController.sections objectAtIndex:section];
return [sectionInfo name];
}
I have also tried subclassing the data object model and added this to setting the month section:
- (NSString *)monthSection {
#synchronized (self.startTime) {
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:#"MMMM"];
NSString *sectionTitle = [formatter stringFromDate:self.startTime];
return sectionTitle;
}
}
Now when the user selects a table view cell I send the Budget Object to the DetailViewController by sending it the following way:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Budget *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController];
[controller setDetailItem:object];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
}
}
Then in DetailViewController i just use the setter on the startTime:
[startDatePicker setDate:detailItem.startTime];
[[AppDelegate instance] saveContext];
[self totalUpFields];
But once the user changes the Month date to something other than what was initially created it thrown that above exception.
I'm very new to the NSManaged Object structure and I've always used a managed config singleton.
For the changed object content here are the methods:
- (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.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
return;
}
}
- (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] withBudget:anObject];
break;
case NSFetchedResultsChangeMove:
[tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
break;
}
}
Thanks for the help, let me know if i need to add additional details.

I believe this is an issue when moving the last row out of a section. You can resolve it by changing your case NSFetchedResultsChangeMove: to the following:
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
It looks like this happens because the sections are modified before the rows, so the index paths shift and the expected source/destination may no longer be valid.
It's also important to first sort by your sectionNameKeyPath:
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"monthSection" ascending:NO];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"startTime" ascending:NO];
[fetchRequest setSortDescriptors:#[sortDescriptor1, sortDescriptor2]];
You will need to add monthSection as a property on your Core Data model and set it when inserting/updating, instead of the dynamic method you're currently using in your subclass (otherwise you'll get an exception when performing the fetch).

Related

Switching between NSFetchedResultsControllers using UISegmentedControl in UITableView

I been working in a project where i use core data to store some information, I have a UITableViewController with a UISegmentedControl and two NSFetchedResultsController because i need to fetched different data when i switch the segmented control.
When I first open the table view i can see the data corresponding to the first index of the segmented controller, but when i switch to the second index the data isn´t show and when i go back to the first index of the segmented control the data isn't show too.
This is the code
#import "TurnHistoryVC.h"
#import "AppDelegate.h"
#import "Turn.h"
#import "TurnInfoVC.h"
#import "Favorite.h"
#interface TurnHistoryVC ()
#property (nonatomic, retain) NSFetchedResultsController *currentFRC;
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, strong) NSFetchedResultsController *favoritesResultsController;
#end
#implementation TurnHistoryVC
#synthesize fetchedResultsController = _fetchedResultsController;
#synthesize favoritesResultsController = _favoritesResultsController;
- (void)viewWillAppear:(BOOL)animated{
NSInteger *turn_id = [[NSUserDefaults standardUserDefaults] integerForKey: #"turn_noti_id"];
if (turn_id != 0) {
[self performSegueWithIdentifier:#"myTurn" sender:self];
} else {
id delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = [delegate managedObjectContext];
NSError *error;
self.currentFRC = self.fetchedResultsController;
if (![[self currentFRC] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
[self.tableView reloadData];
[super viewWillAppear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Atrás" style:UIBarButtonItemStylePlain target:nil action:nil];
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.currentFRC sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.currentFRC sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell.
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Turn *turn = [self.currentFRC objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:#"Turno no. %#", turn.turn_number];
cell.detailTextLabel.text = turn.companie_info;
cell.tag = [turn.turn_id intValue];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// Display the authors' names as section headings.
// return #"";
return [[[self.currentFRC sections] objectAtIndex:section] name];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"cell id: %i", selectedCell.tag);
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
self.editButtonItem.title = #"Listo";
// [tableView ]
// Delete the managed object.
NSManagedObjectContext *context = [self.currentFRC managedObjectContext];
[context deleteObject:[self.currentFRC objectAtIndexPath:indexPath]];
NSError *error;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
// abort();
}
}
}
//Metodo para cambiar el texto del boton para borrar favoritos
-(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
return #"Elimiar";
}
-(void)toggleEdit{
[self.tableView setEditing:!self.tableView.editing animated:YES];
if (self.tableView.editing){
[self.navigationItem.leftBarButtonItem setTitle:#"Listo"];
}else{
[self.navigationItem.leftBarButtonItem setTitle:#"Editar"];
}
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([sender isKindOfClass:[UITableViewCell class]]){
if ([segue.destinationViewController isKindOfClass:[TurnInfoVC class]]){
TurnInfoVC *turnInfoVC = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
UITableViewCell *selectedCell = [self.tableView cellForRowAtIndexPath:indexPath];
turnInfoVC.turn_id = selectedCell.tag;
}
}
}
#pragma mark - Cambio de turnos pendientes a finalizados
- (IBAction)btnTurnTypes:(UISegmentedControl *)sender {
NSError *error;
NSLog(#"cambie de tab 0");
switch (sender.selectedSegmentIndex) {
case 0:
{
self.currentFRC = self.fetchedResultsController;
[self.tableView reloadData];
}
break;
case 1:
{
self.currentFRC = self.favoritesResultsController;
[self.tableView reloadData];
NSLog(#"cambie de tab 1");
}
break;
default:
break;
}
[self.tableView reloadData];
}
#pragma mark - Fetched results controller
/*
Returns the fetched results controller. Creates and configures the controller if necessary.
*/
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
}
NSLog(#"turnos pendientes");
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Turn"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"turn_confirmation==0 AND turn_status==1"];
fetchRequest.predicate = predicate;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Turn" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *categoryDescriptor = [[NSSortDescriptor alloc] initWithKey:#"companie_info" ascending:YES];
NSSortDescriptor *companyDescriptor = [[NSSortDescriptor alloc] initWithKey:#"turn_number" ascending:YES];
NSArray *sortDescriptors = #[categoryDescriptor, companyDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"companie_info" cacheName:#"Root"];
_fetchedResultsController.delegate = self;
self.currentFRC = _fetchedResultsController;
return _fetchedResultsController;
}
- (NSFetchedResultsController *)favoritesResultsController {
if (_favoritesResultsController != nil) {
}
NSLog(#"turnos finalizados");
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Turn"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"turn_confirmation==0 AND turn_status==0"];
fetchRequest.predicate = predicate;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Turn" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *categoryDescriptor = [[NSSortDescriptor alloc] initWithKey:#"companie_info" ascending:YES];
NSSortDescriptor *companyDescriptor = [[NSSortDescriptor alloc] initWithKey:#"turn_number" ascending:YES];
NSArray *sortDescriptors = #[categoryDescriptor, companyDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller.
_favoritesResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"companie_info" cacheName:#"Root"];
_favoritesResultsController.delegate = self;
self.currentFRC = _favoritesResultsController;
return _favoritesResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
NSLog(#"actualizando fuera del if");
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
if (controller == self.currentFRC) {
NSLog(#"actualizando dentro del if");
[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:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
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
I already try this solution, but i can't get it to work, thanks

Core Data NSFetchedResultsControllerDelegate Issue

I am currently attempting to make an iPhone app and have stumbled upon a little bump. I keep getting an error when using the NSFetchedResultsControllerDelegate and anywhere that I use the NSFetchedResults controller. As I am implementing Core Data in my code, it is essential I get this to work. Any feedback is welcome.
Here is the code:
ClassTableViewController.h
#import <UIKit/UIKit.h>
#import "Classes.h"
//#protocol NewClassViewControllerDelegate;
#interface NewClassViewController : UIViewController
//#property (weak, nonatomic) id <NewClassViewControllerDelegate> delegate;
#property (weak, nonatomic) IBOutlet UITextField *classTextField;
#property (weak, nonatomic) IBOutlet UITextField *periodTextField;
#property (strong, nonatomic) Classes *classID;
- (IBAction)saveClass:(id)sender;
#end
ClassTableViewController.m
#import "ClassTableViewController.h"
#import "Classes.h"
#import "ClassTableViewCell.h"
#import "NewClassViewController.h"
#interface ClassTableViewController ()
- (void)configureCell:(ClassTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
#end
#implementation ClassTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
#pragma mark - Segue
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ( [[segue identifier] isEqualToString:#"ShowClass"] ){
NewClassViewController *dvc = (NewClassViewController *)[segue destinationViewController];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Classes *classID = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[dvc setClassID:classID];
} else if ([[segue identifier] isEqualToString:#"addClass"]) {
NewClassViewController *dvc = (NewClassViewController *)[[segue destinationViewController] topViewController];
[dvc setClassID:[NSEntityDescription insertNewObjectForEntityForName:#"Classes" inManagedObjectContext:self.managedObjectContext]];
}
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(firstDone)];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void) firstDone {
[self.navigationController dismissModalViewControllerAnimated:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ClassesCell";
ClassTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[ClassTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(ClassTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Classes *classID = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.classLabel.text = classID.classTitle;
cell.periodLabel.text = classID.period;
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Classes " inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"period" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[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]) {
/*
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();
}
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:[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];
}
/*
// 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;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
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
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
/*
#pragma mark - Navigation
// In a story board-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Thanks for providing some code and asking for any feedback. The header file is confusing for me. I recommend you walk through the tutorial on this site.
Here's a snippet to get you started but walking through the code on the site is your best bet. Afterwards you can come back with specific questions and I will be happy to follow-up. If the tutorial is enough to get you going please feel free to accept this answer.
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FailedBankInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"details.closeDate" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}

implementing NSFetchedResultsController

I'm having some problems implementing NSFetchedResultsController for something very simple. I'm just trying to learn how it works. All I want to do is display the firstName attribute of Person entities in a table view.
As far as I know I've implemented all the methods required but nothing is showing up in the table view. The method -tableView:cellForRowAtIndexPath: method isn't even being called.
Here is my code:
#implementation MyTableViewController
#synthesize managedObjectContext = _managedObjectContext;
#synthesize fetchedResultsController = _fetchedResultsController;
-(NSFetchedResultsController*) fetchedResultsController
{
if (!_fetchedResultsController)
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
}
return _fetchedResultsController;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)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];
}
Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = person.firstName;
return cell;
}
any help would be hugely appreciated! many thanks, Alex
You have to call [NSFetchedResultsController performFetch:] and implement its 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];
}
See more at: http://www.raywenderlich.com/999/core-data-tutorial-for-ios-how-to-use-nsfetchedresultscontroller
Try getting your NSString from your Person object and via the code here:
NSString *firstName = ((Person *)[personArray objectAtIndex:indexPath.row]).firstName;
Mind you, my suggested line is assuming that you output your NSFetchRequest to a NS(Mutable)Array called personArray.
Hopefully that helps, otherwise let me know.

NSFetchedResultsController objectAtIndex, objectAtIndexPath, indexPathForObject inconsistencies

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

Core Data reordering in Multiple sections

In the following code reordering of rows in UITableView only works in the first section.
After reordering rows in sections other than the first ,when the view reappears the rows go back to their original order
Ive googled to no avail.
Can anyone please help me get reordering working in all sections?
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
userDrivenDataModelChange = YES;
NSMutableArray *things = [[__fetchedResultsController fetchedObjects] mutableCopy];
// Grab the item we're moving.
NSManagedObject *thing = [[self fetchedResultsController] objectAtIndexPath:sourceIndexPath];
// Remove the object we're moving from the array.
[things removeObject:thing];
// Now re-insert it at the destination.
[things insertObject:thing atIndex:[destinationIndexPath row]];
// All of the objects are now in their correct order. Update each
// object's displayOrder field by iterating through the array.
int i = 0;
for (NSManagedObject *mo in things)
{
[mo setValue:[NSNumber numberWithInt:i++] forKey:#"displayOrder1"];
}
[things release], things = nil;
[__managedObjectContext save:nil];
userDrivenDataModelChange = NO;
}
- (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];
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
// Edit the entity name as appropriate.
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Client" inManagedObjectContext:context]];
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"displayOrder1" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"area" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor2 ,sortDescriptor ,nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptor2 release];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"area" cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptors release];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
sectionInsertCount = 0;
if (userDrivenDataModelChange)return; {
[self.tableView beginUpdates];
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
if (userDrivenDataModelChange)return;{
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
{
if (userDrivenDataModelChange)return; {
UITableView *aTableView = self.tableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[aTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[aTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[aTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[aTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
if (userDrivenDataModelChange) return;
[self.tableView endUpdates];
}
- (void)saveContext {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
#end
Since your things array is a mutable copy of the fetched results controller's fetchedObjects, none of the manipulations you do to things has any result on fetchedObjects.
Only the manual change shows up because all that change occurs in the fetched results controller's didChange... methods.
Edit:
Your problem is here:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"displayOrder1" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"area" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor2 ,sortDescriptor ,nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptor2 release];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"area" cacheName:#"Root"];
From the NSFetchedResultsController docs:
sectionNameKeyPath ... If this key path is not the same as that
specified by the first sort descriptor in fetchRequest, they must
generate the same relative orderings. For example, the first sort
descriptor in fetchRequest might specify the key for a persistent
property; sectionNameKeyPath might specify a key for a transient
property derived from the persistent property.
Your primary sort key is displayOrder but your sectionNameKeyPath is area and I doubt they produce the same sort order.
It is usually bad practice to put an interface function such as displayOrder into the data model. What happens if you have multiple tables, all with different orders? Unless the ordering is arbitrary and something that the app needs to persist, don't create the attribute.
Also, in two places in the code, you have this construct:
if (userDrivenDataModelChange)return;{
//...
}
While syntactically legal, this is just an bug waiting to happen. If the statement is true, the method returns immediately. If false, the block executes. That is an ugly, ugly construction that is way to easy to misread. Plus, you are issuing a void return. The compiler will warn you about this and you should pay attention to it.
Re: "an explanation why it only works in the first section and not in the others"
Scenario:
Section 1
objectA at indexPath.section = 0, indexPath.row = 0
objectB at indexPath.section = 0, indexPath.row = 1
objectC at indexPath.section = 0, indexPath.row = 2
Section 2
objectAA at indexPath.section = 1, indexPath.row = 0
objectBB at indexPath.section = 1, indexPath.row = 1
objectCC at indexPath.section = 1, indexPath.row = 2
When you copy objects into mutable array, your are "loosing" indexPath.section and your array looks like:
objectA at index 0
objectB at index 1
objectC at index 2
objectAA at index 3
objectBB at index 4
objectCC at index 5
When you remove and insert an object in 1st section you insert it at indexPath.row from 0 to 2 into your array indexes from 0 to 2, which is correct so it works
When you remove and insert an object in 2nd section you still insert it at indexPath.row from 0 to 2 (but indexPath.section 1 already) but your array does not have a section. so it is again inserted at indexes from 0 to 2, instead of 3 to 5, so it does not sort correctly