implementing NSFetchedResultsController - objective-c

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.

Related

TableView sections with NSManaged Object Context Throws Exception

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).

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;
}

How to inform NSFetchedResultsController about [NSManagedObjectContext save]

I've the following question in Objective-C. When I delete an object from my ManagedObjectContext an saved this change, how can I inform my fetchedResultsController to perform a new fetch? In my understanding it is not correct to send performFetch-Message agian.
Thanks!
using this delegate methods you can make it posible
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[self managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
And set the value of the parameter cacheName to nil. it avoids crash and keeps only current data after ManagedObjectContext change
#pragma mark -
#pragma mark Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableViews = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableViews insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableViews deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[_delegate configureCell:[tableViews cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableViews deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableViews insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionMiddle
animated:NO];
}
this code automatically update and reload when you add,delete or update the data in NSFetchedResultsController and UITableViewController
you can use postNotificaton for which you should already have an observer set in your controller which will initiate the fetch request again.

How do I reload a UITableView's data when I don't know how many sections there will be?

I'm a bit confused on how I can reload the cells in a UITableView after it's data has been changed. In particular, the confusion I'm having is that the new data could have more or less sections than what is currently on display. I know there's the reloadSections:withRowAnimation: method, but that requires a 1:1 replacement, where I may or may not have that.
I just want to tell the UITableView to scrap everything and reload as if for the first time. I'd appreciate someone shedding some light on this.
Thanks in advance!
UPDATE
Here's the code I'm using which I've found the issue to be the cells not dequeueing after reloadData is called...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
NSLog(#"%#", cell);
if (cell == nil) {
NSLog(#"%d; %d", indexPath.section, vehicle.inventoryCategoriesCount);
if (indexPath.section < vehicle.inventoryCategoriesCount) {
NSLog(#"Grouped cell");
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"Cell"] autorelease];
NSString *model = [[[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"category == %#", [vehicle.inventoryCategories objectAtIndex:indexPath.section]]] valueForKeyPath:#"#distinctUnionOfObjects.model"] objectAtIndex:indexPath.row];
cell.textLabel.text = model;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d", [[vehicle.inventory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"model == %#", model]] count]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
} else {
NSLog(#"Remove cell");
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"] autorelease];
cell.textLabel.text = #"Remove an Item";
cell.textLabel.textColor = [UIColor redColor];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
NSLog(#"Adding a cell");
return cell;
}
You can call reloadData on the table view.
[myTableView reloadData];
The documentation is here:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html
If you are using core data and the fetched results controller, it's very straightforward. In fact, I believe you can see this if you build a sample core data project with a table view in it.
Two different methods come to mind:
controller didChangeSection is probably what you're looking for. The first is part of the fetchedresultscontroller delegate protocol. The second is part of the tableviewdatasource protocol.
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.noteTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.noteTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
the other is:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[fetchedResultsController sections] count];
}