NSFetchedResultsController requires a non-nil fetch request and managedObjectContext error - ios

Using core data on a on an application that has tabbed views. The second tab loads the core data, no errors show up in Xcode or when I run the app but when I click on the second tab the app crashes with the error "An Instance of NSFetchedResultsController requires a non-nil fetch request and managedObjectContext.
I'm new to core data and really struggling with this error so would appreciate any help I can get. The implementation file has the following code
- (void)setupFetchedResultsController
{
// 1 - Decide what Entity you want
NSString *entityName = #"EatCategory"; // Put your entity name here
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 3 - Filter it if you want
//request.predicate = [NSPredicate predicateWithFormat:#"EatCategory.name = Blah"];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupFetchedResultsController];
if ([[self.fetchedResultsController fetchedObjects] count] == 0) {
NSLog(#"No Results were fetched so nothing will be given to the table view");
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Eat Category Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// configure the cell...
EatCategory *eatcategory = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = eatcategory.name;
return cell;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.tableView beginUpdates]; // Avoid NSInternalInconsistencyException
// Delete the role object that was swiped
EatCategory *eatCategoryToDelete = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Deleting (%#)", eatCategoryToDelete.name);
[self.managedObjectContext deleteObject:eatCategoryToDelete];
[self.managedObjectContext save:nil];
// Delete the (now empty) row on the table
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self performFetch];
[self.tableView endUpdates];
}
}

Are you sure you are setting the managed object context properly? It seems that the fetch request is ok but what about the context? If the context is not set properly this could lead to that error.
Do you inject the context from an external object? If yes, how is declared the managedObjectContext property?
For example:
// from an external object
YourController *controller = ... // alloc-init the controller
controller.managedObjectContext = self.managedObjectContext;
// within your controller .h
#property (strong, nonatomic) NSManagedObject* managedObjectContext; // or retain if you don't use ARC
// within your controller .m
#synthesize managedObjectContext;
You could also grab the main context inside your controller from the application delegate (if you have declared it there) like the following:
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext* managedObjectContext = delegate.managedObjectContext;
but this could lead to a more rigid application design.
Some notes
Instead of using commitEditingStyle you could "register" for NSFetchedResultsControllerDelegate callbacks. This class has been created to deal with changes in table views. You could use it and respond in different manner for table changes.
Here the class reference for NSFetchedResultsControllerDelegate.
Hope it helps.

Related

Core Data and NSFRC showing entries but not persisting through every launch and a Key-Value Coding Error

I am building up a very simple application, allowing users to browse leaflets and videos within the application on a particular topic. One of the features I'm bringing is being able to mark a leaflet or video as a favourite.
The application is UITabBar with 5 tabs and every tab being represented by a UITableViewController. When the user taps to hold on a cell in a tab, it marks it as "starred" and with the use of Core Data and NSFetchedResultsController, the idea is for that entry to appear in the Starred tab.
This is my simple Core Data model:
So when the user taps and holds a cell in any one of the 4 tabs, this is the code I run:
- (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerRightUtilityButtonWithIndex:(NSInteger)index
{
switch (index) {
case 0:
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
CustomLeafletVideoTableViewCell *cell = (CustomLeafletVideoTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
NSString *cellTitle = cell.customCellLabel.text;
[self moreButtonPressed:cellTitle];
[cell hideUtilityButtonsAnimated:YES];
break;
}
default:
break;
}
}
- (void)cellPressed:(NSString *)passedString
{
NSManagedObjectContext *context = [self managedObjectContext];
Item *item = [NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:context];
Videos *videos = [NSEntityDescription insertNewObjectForEntityForName:#"Videos" inManagedObjectContext:context];
videos.title = passedString;
item.video = videos;
NSLog(#"Passed String = %#", videos.title);
}
I have created a FavouritesTableViewController class and here's the main code:
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)])
{
context = [delegate managedObjectContext];
}
return context;
}
- (NSFetchedResultsController *)fetchedResultsController
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Videos" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
NSPredicate *d = [NSPredicate predicateWithFormat:#"items.video.#count !=0"];
[fetchRequest setPredicate:d];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:NO];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
fetchRequest.fetchBatchSize = 20;
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error])
{
//exit(-1);
}
self.favouritesTableView.dataSource = self;
self.favouritesTableView.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.favouritesTableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
#pragma mark Cell Configuration
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
CustomLeafletVideoTableViewCell *customCell = (CustomLeafletVideoTableViewCell *)cell;
Videos *videos = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"What is the video . title %#", videos.title);
customCell.customCellLabel.text = videos.title;
//
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"FavouritesCell";
CustomLeafletVideoTableViewCell *cell = (CustomLeafletVideoTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
#pragma mark NSFetchedResultsControllerDelegate Methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
// The boiler plate code for the NSFetchedResultsControllerDelegate
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;
default:
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];
}
Issues
My issues seem to stem from the NSFetchedResultsController. In that method, if I leave it as it is with the predicate, when I tap to hold the cell on the other tab, the app crashes with:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Videos 0x7ffb48d0b910> valueForUndefinedKey:]: the entity Videos is not key value coding-compliant for the key "#count".'
If I remove the predicate line:
// NSPredicate *d = [NSPredicate predicateWithFormat:#"items.video.#count !=0"];
// [fetchRequest setPredicate:d];
when I tap to hold a cell, it marks it as favourite and then when I go to the favourites tab, the entry is there. However, if I launch the app again, the entries in the Favourites tab have gone.
I'm not quite sure what's going on here. Essentially, the Favourites tab is a place for storing the starred items from the user from the other tabs. Do I need a predicate and if I don't, why is the data not persisting through each launch?
The app was set up with Core Data selected, so the AppDelegate has been set up appropriately.
Any guidance on this would be really appreciated.
The video property of Item is a to-one relationship. Basically it's a pointer to an object (or nil). You can't count it, it's not a collection of objects.
So your key path items.video.#count and in particular the video.#count doesn't make sense, hence the crash.
If you want to check if there is a video for a given Item, use #"items.video != nil".
Also you should probably follow conventions and use singular for your objects names (Leaflet and Video) and singular for to-one relationships (item instead of items).

Delete NSManagedObject from managedObjectContext & tableView

I have a UITableView that displays attributes of entities that are in an array created with an NSFetchRequest. My instructor tells me for my purposes, I do NOT need a fetchedResultsController--so I do NOT need to add NSFetchedResultsControllerDelegate methods to my UITableViewController class. His analogy was putting a Porsche engine in a VW Beetle.
My tableView has 2 sections (0 and 1). Everything works fine until I try to delete objects from section 1 (section 0 is static).
With this commitEditingStyle code, the app crashes with his error:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:],
/SourceCache/UIKit_Sim/UIKit-3347.40/UITableView.m:1623
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete && indexPath.section == 1) {
// Delete cell
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
// Delete LocationCategory from managedObjectContext
LocationCategory *categoryToDelete = [self.locationCategories objectAtIndex:indexPath.row];
[self.managedObjectContext deleteObject:categoryToDelete];
[self.managedObjectContext save:nil];
} 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
}
}
When I restart the app, the locationCategory (my NSManagedObject) is no longer in the managedObjectContext. I know I've got a problem updating the tableView, but I don't know how to resolve it. I've dug around, tried a bunch of prospective solutions, but nothing resolves the crashing.
Here's how my array is created:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.title = #"Select a Category";
// Core Data
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
self.managedObjectContext = [appDelegate managedObjectContext];
// Fetch LocationCategories & dump them in an array
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"LocationCategory" inManagedObjectContext:self.managedObjectContext]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"categoryName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
[fetchRequest setSortDescriptors:sortDescriptors];
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
self.locationCategories = categories;
[self.tableView reloadData];
}
Update:
Here's what the code looked like at the end when it worked:
// This was previously an NSArray. It needs to be an NSMutableArray
#property (nonatomic, strong) NSMutableArray *locationCategories;
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete && indexPath.section == 1) {
// Delete LocationCategory from managedObjectContext
LocationCategory *categoryToDelete = [self.locationCategories objectAtIndex:indexPath.row];
[self.managedObjectContext deleteObject:categoryToDelete];
[self.locationCategories removeObjectAtIndex:indexPath.row];
[self.managedObjectContext save:nil];
// Delete cell
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
} 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
}
}
Presumably your tableView datasource methods use self.locationCaregories. If so, the problem is that you don't remove the relevant Category from that array (although you delete it from the managedObjectContext, it remains in the array).
Now, because self.locationCategories is an NSArray, it is immutable, so you can't just remove the relevant item. You could reperform the fetch immediately after you delete the object, but that seems a bit inefficient. I recommend instead that you make self.locationCategories an NSMutableArray. You will need to amend the property definition, and also the line in viewWillAppear where you first set it:
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
self.locationCategories = [categories mutableCopy];
Then in the commitEditingStyle method, you can remove the relevant object from the array:
LocationCategory *categoryToDelete = [self.locationCategories objectAtIndex:indexPath.row];
[self.locationCategories removeObjectAtIndex:indexPath.row];
And as thorb65 says, move the code to delete the rows to the end of the method - you should get the data right, before you update the table.

null indexPaths when deleting an object

I can't figure this out, but I seem to have a null indexPath when I delete an object from the NSFetchedResultsController.
When I delete my object, I do this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the object
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
[self saveContext];
}
}
Setting up NSFetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setFetchBatchSize:20];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Route" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *nameSort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = #[nameSort];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Routes"];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error with NSFetchedResultsController: %#", [error description]);
abort();
}
return _fetchedResultsController;
}
This is where the failure occurs:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
// Data was inserted - insert the data into the table view
case NSFetchedResultsChangeInsert: {
[self.savedRoutesTableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
// Data was deleted - delete the data from the table view
case NSFetchedResultsChangeDelete: {
[self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case NSFetchedResultsChangeUpdate: {
SavedRoutesTableViewCell *cell = (SavedRoutesTableViewCell *)[self.savedRoutesTableView cellForRowAtIndexPath:indexPath];
[cell configureCellWithRoute:[controller objectAtIndexPath:newIndexPath]];
break;
}
case NSFetchedResultsChangeMove: {
[self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.savedRoutesTableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
default:
break;
}
}
In the didChangeObject method, both my indexPath and newIndexPath are nil. I can NSLog my object and I do see the entity. It crashes in the [self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; method with the exception:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0] with userInfo (null)
When I save this object, I save it like this:
self.route.name = routeName;
NSManagedObjectContext *tempContext = [self.route managedObjectContext];
[tempContext performBlock:^{
NSError *error = nil;
if (![tempContext save:&error]) {
NSLog(#"an error occurred: %#", [error localizedDescription]);
}
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"error in main context: %#", [error localizedDescription]);
}
}];
}];
I'm not really sure where else to debug this since the NSFetchedResultsController just isn't returning me the indexPath for the deleted object. Any thoughts? Thanks in advance.
Edit:
Well I found the culprit causing the error, but I'm not sure why it does. Basically I have a ViewController that receives either a Route entity from the main MOC if it's Editing the route, or it inserts a new one if you are creating a new route. So in that viewController, if I'm editing a route, because I am trying to use two MOCs, one temp, and one main for its parent so I can easily throw away stuff if the user decides to cancel and not create a new route, I needed to transfer over that route to the other context to make other code I have work. So that "transfer" looks like:
NSManagedObjectContext *moc = _route.managedObjectContext;
NSManagedObjectID *routeId = [_route objectID];
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.tempContext.parentContext = moc;
NSManagedObject *localRoute = [self.tempContext objectWithID:routeId];
self.route = localRoute;
With this code, my adding on locations to an existing route works now that the locations are in the same contexts, but somehow it messes up deleting an existing route from the main MOC. Not sure why and what the best solution is.
Had the same problem and solved it by ensuring that NSFetchedResultsController always uses the same NSManagedContext. For example if u fetch object from a database with a fetch controller and later you want to delete that object, make sure that the fetch controller uses the same managed context it was using during fetching.
NSFetchedResultsController is optimised for working with UITableView and UITableView is user interface component, and user interface should always be handled by main thread, so there is no need to create new NSManagedContext every time you go into fetch... So implementing this code should fix this problem:
#synthesize fetchedResultsController = __fetchedResultsController;
#synthesize managedObjectContext = __managedObjectContext;
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = ap.persistentStoreCoordinator;
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
ap is a pointer to AppDelegate:
ap = [[UIApplication sharedApplication] delegate];
What this code does is creates one instance of MOC and later reuses it. Warning: this is not threadsafe, and it doesn have to be (cause you should use it with main thread only), so if you missuse it it will not work...
Use that:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
//create __fetchedResultsController
// do some fetching
return __fetchedResultsController;
}
So when you want to populate table:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [__fetchedResultsController.fetchedObjects count];
}
- (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];
}
NSManagedObject *managedObject = [__fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [managedObject valueForKey:#"smth."];
return cell;
}
If your dataset changes just:
__fetchedResultsController = nil;
[tableView reloadData]
And everything including NSFetchedResultsControllers delegat methods will work fine, no nil indexPaths and so on...
Hope I helped someone...
NSManagedObjectContext *moc = _route.managedObjectContext;
NSManagedObjectID *routeId = [_route objectID];
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.tempContext.parentContext = moc;
NSManagedObject *localRoute = [self.tempContext objectWithID:routeId];
self.route = localRoute;
Had to get the context associated with the route.

How to fetch data from core data and display in table View iOS 6

I have successfully stored address book data in Core Data but i m not able to retrieve it and display it in tableView .What am i missing ?
This is how i fetch data from core data.
-(void)fetchFromDatabase
{
AddressBookAppDelegate *appDelegate =[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"AddressBook" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSError *error;
self.arrayForTable = [context executeFetchRequest:request error:&error];
NSLog(#"fetched data = %#",[self.arrayForTable lastObject]); //this shows the data
[self.tableView reloadData];
And this is my table view configuration.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.arrayForTable count];
}
- (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];
if ([self.arrayForTable count]>0)
{
NSLog(#" table view content = %#",[self.arrayForTable lastObject]);// this doesn't log
AddressBook *info = [self.arrayForTable objectAtIndex:indexPath.row];
cell.textLabel.text = info.firstName;
}
}
return cell;
}
As it turned out in the discussion, self.arrayForTable was replaced by an empty array in viewWillAppear after fetching the objects in fetchFromDatabase.
Another problem was that each run of the program created new objects in the database,
leading to duplicate objects. You can either
delete all objects before inserting the new objects, or
for each name, check if a matching object already exists in the database, and insert a new one only if necessary.
More advanced techniques are described in "Implementing Find-or-Create Efficiently" in the "Core Data Programming Guide".

Why is NSFetchedResultsController assigning fetched objects to multiple UITableView sections?

In another question of mine concerning the addition of an insert row in a UITableView backed by Core Data, I mentioned that my NSFetchedResultsController is assigning each object it fetches to a separate section in my UITableView. I assumed this was merely the default behavior, but Marcus S. Zarra said there might be something wrong with my configuration of the controller or my datasource delegate methods. I admit, my code feels a bit like Frankenstein with parts pulled from the Apple docs and numerous tutorials. This is my first program and my first time using Core Data, so please be gentle ;)
My table view controller's header is as follows:
#import <UIKit/UIKit.h>
#import "RubricAppDelegate.h"
#interface ClassList : UITableViewController {
NSMutableArray *classList;
NSFetchedResultsController *fetchedResultsController;
NSManagedObjectContext *managedObjectContext;
}
#property(nonatomic,retain) NSMutableArray *classList;
#property(nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
#property(nonatomic, retain) NSManagedObjectContext *managedObjectContext;
- (IBAction) grade:(id)sender;
#end
My implementation file includes a bunch of dummy test data. I included that in case I am using incorrect methods for instantiating the Core Data objects. Basically, I want to know if my NSFetchedResultsController should not be returning my objects (in this case, instances of myClass) into separate sections. If so, what am I doing to create that problem?
The ultimate goal at the moment is for me to be able to add an insert cell at the top of my table (I know that putting it at the bottom is "standard," but I like how it looks in the apps that do it the other way around). You will notice my -tableView:editingStyleForRowAtIndexPath: sets the cell style of section 0 to insert, but of course I need to figure out how to start the listing of myClass.classTitle at cell 1 instead of cell 0 (which is why I want to determine if having each object assigned to its own section is normal).
Here is my implementation file:
#import "ClassList.h"
#import "ClassRoster.h"
#import "RubricAppDelegate.h"
#import "Student.h"
#import "myClass.h"
#implementation ClassList
#synthesize classList;
#synthesize fetchedResultsController;
#synthesize managedObjectContext;
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.editing = YES;
RubricAppDelegate *appDelegate = (RubricAppDelegate *)[[UIApplication sharedApplication] delegate];
managedObjectContext = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"myClass" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
//test data
myClass *newClass = (myClass *) [NSEntityDescription insertNewObjectForEntityForName:#"myClass" inManagedObjectContext:managedObjectContext];
newClass.classTitle = #"UFDN 1000";
NSNumber *ID = [NSNumber numberWithInt:1];
newClass.classID = ID;
Student *newStudent = (Student *) [NSEntityDescription insertNewObjectForEntityForName:#"Student" inManagedObjectContext:managedObjectContext];
newStudent.classID = ID;
newStudent.studentName = #"Andy Albert";
newStudent.studentUsername = #"albera";
[newClass addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"Bob Dole";
newStudent.studentUsername = #"doleb";
[newClass addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"Chris Hanson";
newStudent.studentUsername = #"hansoc";
[newClass addStudentsObject:newStudent];
myClass *newClass2 = (myClass *) [NSEntityDescription insertNewObjectForEntityForName:#"myClass" inManagedObjectContext:managedObjectContext];
newClass2.classTitle = #"UFDN 3100";
ID = [NSNumber numberWithInt:2];
newClass2.classID = ID;
newStudent.classID = ID;
newStudent.studentName = #"Danny Boy";
newStudent.studentUsername = #"boyd";
[newClass2 addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"James Matthews";
newStudent.studentUsername = #"matthj";
[newClass2 addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"Aaron Todds";
newStudent.studentUsername = #"toddsa";
[newClass2 addStudentsObject:newStudent];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"classID" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
NSError *error;
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"classTitle" cacheName:nil];
[fetchedResultsController performFetch:&error];
UIBarButtonItem *gradeButton = [[UIBarButtonItem alloc]
initWithTitle:#"Grade"
style:UIBarButtonItemStylePlain
target:self
action:#selector(grade:)];
self.navigationItem.rightBarButtonItem = gradeButton;
[gradeButton release];
}
- (IBAction) grade:(id)sender {
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"Number of sections = %d", [[fetchedResultsController sections] count]);
return ([[fetchedResultsController sections] count]);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
id <NSFetchedResultsSectionInfo> myClass = [[fetchedResultsController sections] objectAtIndex:section];
NSLog(#"Number of classes = %d", [myClass numberOfObjects]);
return [myClass numberOfObjects];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
myClass *theClass = [fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Class name is: %#", theClass.classTitle);
cell.textLabel.text = theClass.classTitle;
}
return cell;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
return UITableViewCellEditingStyleInsert;
}
else return UITableViewCellEditingStyleDelete;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
myClass *result = (myClass *)[fetchedResultsController objectAtIndexPath:indexPath];
[managedObjectContext deleteObject:result];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
}
}
#pragma mark -
#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];
[detailViewController release];
*/
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
- (void)dealloc {
[classList release];
[super dealloc];
}
#end
My RubricAppDelegate is essentially identical to the Apple documentation for setting up Core Data NSManagedObjectContext, NSPersistentStoreCoordinator, etc. However, if you think there might be a problem in there, just let me know and I'll post it.
Edit: I forgot to mention two reasons I know each object is being assigned to a different section.
1) NSLog(#"Number of sections = %d", [[fetchedResultsController sections] count]); inside of -numberOfSectionsInTableView: returns the number of myClass objects I have.
2) If I set -numberOfSectionsInTableView: to return 1, my table only displays one object and cuts the rest out.
You have sections because you tell the fetched results controller to create them by passing a non-Nil value for sectionNameKeyPath: when you initialize the FRC.
Change:
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"classTitle" cacheName:nil];
...to:
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
... and the sections will go away. Otherwise, the FRC will create one section for each value of the classTitle attribute in the store. If each myClass instance has a different value for classTitle each instance will have its own section in the tableview.

Resources