NSFetchedResultsController doesn't fetch results even though items are created in the database - objective-c

I'm new to iOS, so apologies if this is brain dead simple... I've been iteratively working through some small proof of concept apps before starting to implement my full app so that it wouldn't be as overwhelming. I had my table view working fine when I created it following the "Your Second iOS App" tutorial on the Apple website. Now I've tried creating it in a tab bar app though, and I'm seeing problems with the NSFetchedResultsController, and I'm not sure if it's related to something that I'm doing wrong in the Storyboard, or something else.
I have a Tab Bar Controller that connects to a Table View Controller (CatalogViewController.h/m) that is embedded in a Navigation Controller. The Table View Controller is configured to have static cells. In the first static cell I have a push segue to another Table View Controller (FoodCatalogViewController.h/m) which is configured to use dynamic prototypes - this is the view in which I expect to see the objects from my database (from the Food entity - currently just shows name and calories). This view has an "Add" button to create new entries in the database - the add button has a modal segue to another static table view (AddFoodViewController.h/m) that is embedded in it's own navigation controller. I know that the "Add" button is working and that it's view is correctly connecting to the database (i.e. I'm passing/setting the NSManagedObjectContext correctly), because if I open the app's sqlite database file using "SQLite Database Browser", I see the items that I've added in the simulator. I just don't understand why they're not getting displayed in my table view via the NSFetchedResultsController. I stepped through the code using breakpoints and confirmed that the performFetch code is being called in my FoodCatalogViewController's fetchedResultsController function. I added a debug NSLog line in the numberOfRowsInSection code, and it seems to be nil, so I never get into cellForRowAtIndexPath or configureCell. So it looks like the NSFetchedResultsController is the culprit - I just don't know why it's not fetching the results correctly, and what I can do to debug this further. Can anyone help me with this?
In order to pass the Core Data info through the hierarchy, I have the following code snippets:
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
// Setup the Catalogs Tab
UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex:0];
CatalogViewController *catalogViewController = [[navigationController viewControllers] objectAtIndex:0];
catalogViewController.managedObjectContext = self.managedObjectContext;
return YES;
}
CatalogViewController.m (the first table view controller in the sequence - I pass the NSManagedObjectContext through to it):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"BrowseFoodCatalog"]) {
[[segue destinationViewController] setManagedObjectContext:self.managedObjectContext];
}
}
FoodCatalogViewController.h (the second table view controller in the sequence - I use the NSManagedObjectContext to setup the NSFetchedResultsController):
#interface FoodCatalogViewController : UITableViewController <NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
- (void) addFoodWithName:(NSString *)name calories:(NSNumber *)calories;
#end
FoodCatalogViewController.m (the second table view controller in the sequence - I use the NSManagedObjectContext to setup the NSFetchedResultsController):
#interface FoodCatalogViewController () <AddFoodViewControllerDelegate>
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
#end
#implementation FoodCatalogViewController
#synthesize fetchedResultsController = __fetchedResultsController;
#synthesize managedObjectContext = __managedObjectContext;
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Food" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"FoodCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [[managedObject valueForKey:#"name"] description];
NSNumber *calorieNum = [managedObject valueForKey:#"calories"];
cell.detailTextLabel.text = [[calorieNum stringValue] description];
}
Additional Info
Not sure if this is relevant, but in order to get CoreData to automatically be included in my project, I started with the Single View template, but modified it's TemplateInfo.plist to add the following line under the similar line for storyboarding:
<string>com.apple.dt.unit.coreDataCocoaTouchApplication</string>
I'd found this online somewhere in someone's forum or something. Could this have messed up the CoreData somehow?
Additional Code
As requested, here's the code that I use to add new elements to the database:
AddFoodViewController.h:
#import <UIKit/UIKit.h>
#protocol AddFoodViewControllerDelegate;
#interface AddFoodViewController : UITableViewController <UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *foodNameInput;
#property (weak, nonatomic) IBOutlet UITextField *caloriesInput;
#property (weak, nonatomic) id <AddFoodViewControllerDelegate> delegate;
- (IBAction)save:(id)sender;
- (IBAction)cancel:(id)sender;
#end
#protocol AddFoodViewControllerDelegate <NSObject>
- (void)addFoodViewControllerDidCancel:(AddFoodViewController *)controller;
- (void)addFoodViewControllerDidSave:(AddFoodViewController *)controller name:(NSString *)name calories:(NSNumber *)calories;
#end
AddFoodViewController.m:
#import "AddFoodViewController.h"
#implementation AddFoodViewController
#synthesize foodNameInput;
#synthesize caloriesInput;
#synthesize delegate = _delegate;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#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;
}
- (void)viewDidUnload
{
[self setFoodNameInput:nil];
[self setCaloriesInput:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ((textField == self.foodNameInput) || (textField == self.caloriesInput )) {
[textField resignFirstResponder];
}
return YES;
}
- (IBAction)save:(id)sender {
int caloriesInt = [self.caloriesInput.text intValue];
NSNumber *caloriesNum = [NSNumber numberWithInt:caloriesInt];
[[self delegate] addFoodViewControllerDidSave:self name:self.foodNameInput.text calories:caloriesNum];
}
- (IBAction)cancel:(id)sender {
[[self delegate] addFoodViewControllerDidCancel:self];
}
#end
FoodCatalogViewController.m (the AddFoodViewControllerDelegate protocol code to add to the database):
- (void)addFoodViewControllerDidCancel:(AddFoodViewController *)controller {
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (void)addFoodViewControllerDidSave:(AddFoodViewController *)controller name:(NSString *)name calories:(NSNumber *)calories {
if ([name length]) {
[self addFoodWithName:name calories:calories];
[[self tableView] reloadData];
}
[self dismissModalViewControllerAnimated:YES];
}
- (void) addFoodWithName:(NSString *)name calories:(NSNumber *)calories {
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSLog(#"entity name is %#", [entity name]);
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
[newManagedObject setValue:name forKey:#"name"];
[newManagedObject setValue:calories forKey:#"calories"];
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
NSString* uuid = [NSString stringWithString:(__bridge NSString *)uuidStringRef];
[newManagedObject setValue:uuid forKey:#"uuid"];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
More Debug Info
Strange - it looks like the fetchedResultsController isn't working correctly in the FoodCatalogViewController even though the managedObjectContext seems to be working... Here's the modified fetchedResultsController function from FoodCatalogViewController.m with some debug NSLog statements and replacing self.fetchedResultsController with __fetchedResultsController (because I wondered if that was causing the problem).
Here's the output from the fetchedResultsController function NSLog calls:
2012-01-29 10:22:21.118 UltraTrack[19294:fb03] Result: (
"<Food: 0x6e651b0> (entity: Food; id: 0x6e64630 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p1> ; data: <fault>)",
"<Food: 0x6e653e0> (entity: Food; id: 0x6e61870 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p3> ; data: <fault>)",
"<Food: 0x6e65450> (entity: Food; id: 0x6e64420 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p4> ; data: <fault>)",
"<Food: 0x6e654c0> (entity: Food; id: 0x6e64430 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p5> ; data: <fault>)",
"<Food: 0x6e65530> (entity: Food; id: 0x6e64e80 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p2> ; data: <fault>)",
"<Food: 0x6e655b0> (entity: Food; id: 0x6e64e90 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p6> ; data: <fault>)"
)
2012-01-29 10:22:21.907 UltraTrack[19294:fb03] Number or objects: 6
And here's the modified fetchedResultsController function:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Food" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
__fetchedResultsController = aFetchedResultsController;
NSArray *result = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
NSLog(#"Result: %#", result);
NSError *error = nil;
if (![__fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog(#"Number or objects: %d", [__fetchedResultsController.fetchedObjects count]);
return __fetchedResultsController;
}
Someone suggested that the sections were the problem, so I hard-coded numberOfSectionsInTableView to return 1, and then the first object from the fetchResults seems to be handled correctly, but I get the following exception:
2012-01-29 10:29:27.296 UltraTrack[19370:fb03] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 1 in section at index 0'
If I hardcode numberOfRowsInSection to also return 1, then the first object from my database is correctly displayed in the table view. What could be the problem with regard to the sections info in the fetchedResultsController? Could I have setup something incorrectly in the storyboard for the table view with regard to sections?
Here's the 2 table view functions where I've tried the hard-coding:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
//NSLog(#"Number of sections in table view is %d", [[self.fetchedResultsController sections] count]);
//return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"Number or objects: %d", [self.fetchedResultsController.fetchedObjects count]);
// If I return 1, the object is displayed correctly, if I return count, I get the exception
//return 1;
return [self.fetchedResultsController.fetchedObjects count];
// Return the number of rows in the section.
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSLog(#"Number of rows being returned is %d", [sectionInfo numberOfObjects]);
return [sectionInfo numberOfObjects];
}

From your description it seems that the code with the sections is the culprit. But you are not actually using any sections. So try this to simplify:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.fetchedResultsController.fetchedObjects count];
}
If this still returns zero rows, check your fetchedObjects.count in the fetchedResultsController getter method.

Related

How to prepareForSegue with NSFetchedResults drilldown to another tableview with objective c

I know there is probably a very simple solution to this. I'm using the apple iphoneCoreDataRecipes example. I'm trying to drill down from an initial table(RecipeTypeTableViewController) of RecipeTypes (ie Beverage, Dessert, Entree) to the next tableview with listing of those type of Recipes(RecipeListTableViewController). I created a new class RecipeType, thinking I could just use this to return recipes of a selected RecipeType. So far the best I can get is allowing selection but it is showing the same list of all recipes. The original code starts with RecipeListTableViewController using NSFetchedResults to show all recipes. Now I have the initial view of RecipeTypes but I'm not sure how to pass the selected type to RecipeListTableViewController. I need to change RecipeListTableViewController to show list of the selected RecipeType but I'm not sure how to implement this. I think I basically want to pass an NSMutableArray of fetchedresults to another tableview but not sure which direction to take. And I don't want to mess up other working actions in the RecipeListTableViewController, ie search and editing of recipes. Here's images of table action so far [RecipeTypeTableView][1] & [RecipeListTableview][2]. Thank you in advance for any suggestions.
Deleted these
File: RecipeType.h
File: RecipeType.m
I use the NSManagedObject *type from my Recipe Class NSManagedObject to create initial view of recipe types(Appetizer, Bev, etc.) on the RecipeTypeTableViewController -
File: Recipe.h
#interface Recipe : NSManagedObject
#property (nonatomic, strong) NSManagedObject *type;
File: RecipeTypeTableViewController.h
// RecipeTypeTableViewController.h
// Recipes
#import <UIKit/UIKit.h>
#interface RecipeTypeTableViewController : UITableViewController <NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) NSManagedObject *type;
#end
File: RecipeTypeTableViewController.m - the prepare for segue part
#import "RecipeTypeTableViewController.h"
#import "Recipe.h"
#import "RecipeListTableViewController.h"
#interface RecipeTypeTableViewController () <NSFetchedResultsControllerDelegate>
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#end
#implementation RecipeTypeTableViewController
static NSString *MyIdentifier = #"showRecipes";
- (void)viewDidLoad {
[super viewDidLoad];
// register this class for our cell to this table view under the specified identifier 'ARecipeType'
self.title = #"Category";
//[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"ARecipeType"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didChangePreferredContentSize:)name:UIContentSizeCategoryDidChangeNotification object:nil];
// 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;
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.title = #"Category";
}
- (void)didChangePreferredContentSize:(NSNotification *)notification
{
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil];
}
#pragma mark - Table view data source
//- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
//#warning Incomplete implementation, return the number of sections
//return [[self.recipeTypeFetchedResultsController sections] count];
// return 1;
//}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Number of rows is the number of recipe types
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
//return self.recipeTypes.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ARecipeType" forIndexPath:indexPath];
// Configure the cell
NSManagedObject *recipeType = [self.recipeTypes objectAtIndex:indexPath.row];
cell.textLabel.text = [recipeType valueForKey:#"name"];
return cell;
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (_fetchedResultsController == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"RecipeType" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entity);
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Recipe"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
return _fetchedResultsController;
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showRecipes"]) {
NSLog(#"Setting RecipeType for the RecipeListTableViewController");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Recipe *selectedType = [self.fetchedResultsController objectAtIndexPath:indexPath];
RecipeListTableViewController *recipeListViewController = segue.destinationViewController;
recipeListViewController.type = selectedType;
recipeListViewController.managedObjectContext = self.managedObjectContext;
}
}
File: RecipeListTableViewController.h - the NSPredicate part and the FRC cache
#import <UIKit/UIKit.h>
#import "RecipeAddViewController.h"
#interface RecipeListTableViewController : UITableViewController <RecipeAddDelegate, NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) NSManagedObject *type;
File: RecipeListTableViewController.m
#import "RecipeListTableViewController.h"
#import "RecipeDetailViewController.h"
#import "Recipe.h"
#import "RecipeTableViewCell.h"
#import "Recipe+Extensions.h"
#import "TypeSelectionViewController.h"
#import "IngredientDetailViewController.h"
#import "Ingredient.h"
#import "WhereViewController.h"
#import "FavoriteListTableViewController.h"
#import "RecipeTypeTableViewController.h"
#import "RecipeType.h"
#interface RecipeListTableViewController () <NSFetchedResultsControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating>
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, strong) NSArray *filteredList;
#property (nonatomic, strong) NSFetchRequest *searchFetchRequest;
#property (nonatomic, strong) UISearchController *searchController;
typedef NS_ENUM(NSInteger, RecipesSearchScope)
{
searchScopeRecipe = 0,
searchScopeIngredients = 1
};
#end
#implementation RecipeListTableViewController
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (_fetchedResultsController == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipe" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[NSFetchedResultsController deleteCacheWithName:#"Recipe"];
// Create predicate
//if (self.type) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type == %#", self.type];
[fetchRequest setPredicate:predicate];
//}
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Recipe"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
return _fetchedResultsController;
}
/
When I have the FRC cache set to "Recipe". It crashes. It showed this clue to look at cache CoreData: FATAL ERROR: The persistent cache of section information does not match the current configuration. You have illegally mutated the NSFetchedResultsController's fetch request, its predicate, or its sort descri...
If I set the cache to nil, or add [NSFetchedResultsController deleteCacheWithName:#"Recipe"]; before the predicate set, things work as expected. I have this cache in the inital view controller and the second view controller. Maybe that is the problem - I only need cache in the initial view controller?
First, I think your data model needs a little refinement. Each RecipeType can presumably have many associated Recipes. So the relationship from RecipeType to Recipe should be to-many. If you change this in the data model editor, and regenerate the model classes, you should then have a RecipeType class that looks something like this:
#class Recipe;
#interface RecipeType : NSManagedObject
// not sure what purpose this property serves; it might now be superfluous...
#property (nonatomic, strong) NSManagedObject *type;
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSSet<Recipe *> *recipes;
#end
I'll assume for the time being that each Recipe can belong to only one RecipeType. So the inverse relationship, from Recipe to RecipeType should be to-one, and the Recipe class will therefore have a property:
*property (nonatomic, strong) RecipeType *type;
Next, you want your RecipeListTableViewController to display only the Recipes that are related to the relevant RecipeType. To achieve that, you need to add a predicate to the fetched results controller. In the fetchedResultsController method, add:
if (self.recipeType) {
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"type == %#", self.recipeType];
}
(You will also need to amend your search to likewise limit the search to the relevant RecipeType). In the RecipeTypeTableViewContoller, your prepareForSegue needs only to pass the correct RecipeType:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showRecipes"]) {
NSLog(#"Setting RecipeType for the RecipeListTableViewController");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
RecipeType *selectedType = [self.fetchedResultsController objectAtIndexPath:indexPath];
RecipeListTableViewController *recipeListViewController = segue.destinationViewController;
recipeListViewController.recipeType = selectedType;
recipeListViewController.managedObjectContext = self.managedObjectContext;
}
}
Whenever you add a new Recipe, you will need to assign it to the correct RecipeType. So in the RecipeListViewController amend your prepareForSegue to set the relationship:
...
else if ([segue.identifier isEqualToString:kAddRecipeSegueID]) {
// add a recipe
//
Recipe *newRecipe = [NSEntityDescription insertNewObjectForEntityForName:#"Recipe" inManagedObjectContext:self.managedObjectContext];
newRecipe.type = self.recipeType;
UINavigationController *navController = segue.destinationViewController;
RecipeAddViewController *addController = (RecipeAddViewController *)navController.topViewController;
addController.delegate = self; // do didAddRecipe delegate method is called when cancel or save are tapped
addController.recipe = newRecipe;
}

get 2 arrays from core data separate them by nsdate

ok this one should be easy. For creating a app in Xcode. I have one View. This view has two table views. upcomingEventsTableView and expiredEventsTableView. I am pulling data for core data. This data has names "event" and nsDate "eventTimeStart". I want the eventTimeStart NSDate data split from the current time "nowDate". I want to display the past data on the expiredEventsTableView and the NSDates in the future to show up on the upcomingEventsTableView. I think I'm on the right track but having troubles around my sort descriptors area. So far I have the same dates on both tableviews. Just don't know how to separate the dates for each tableview.
#interface EventsViewController ()
#property (nonatomic, strong)NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong)NSFetchedResultsController *fetchedResultsController;
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (weak, nonatomic) IBOutlet UIView *contentView;
#end
#implementation EventsViewController
#synthesize upcomingEventsTableView;
#synthesize expiredEventsTableView;
-(NSManagedObjectContext*)managedObjectContext{
return [(AppDelegate*)[[UIApplication sharedApplication]delegate]managedObjectContext];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.upcomingEventsTableView reloadData];
[self.expiredEventsTableView reloadData];
//scrollview stuff
[super viewDidLayoutSubviews];
[self.scrollView layoutIfNeeded];
self.scrollView.contentSize=self.contentView.bounds.size;
NSError *error = nil;
if(![[self fetchedResultsController]performFetch:&error]){
NSLog(#"Error! %#", error);
abort();
}
}
//upcoming & EXPIRED events tableview
-(void)viewWillAppear:(BOOL)animated{
[self.upcomingEventsTableView reloadData];
[self.expiredEventsTableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
if (upcomingEventsTableView){
return 1;
} else {
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (upcomingEventsTableView){
id<NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections]objectAtIndex:section];
return [sectionInfo numberOfObjects];
}else{
id<NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections]objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
//format the date
-(NSDate *)convertDateToDate:(NSDate *) date
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSDate *nowDate = [[NSDate alloc] init];
[formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[formatter setDateFormat:#"yyyy-MM-d H:m:s"];
NSString * strdate = [formatter stringFromDate:date];
nowDate = [formatter dateFromString:strdate];
return nowDate;
}
#pragma mark - fetched results controller section
-(NSFetchedResultsController*)fetchedResultsController {
if (_fetchedResultsController != nil){
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"eventTimeStart" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
if ([sortDescriptors earlierDate: nowDate] == sortDescriptors) {
NSLog(#"upcoming");
//create nsarray upcoming here and apply to tableview upcomingEventsTableView;
}else{
NSLog(#"expired");
//create nsarray upcoming here and apply to tableview expiredEventsTableView
}
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
#pragma Mark - fetched results controller delegates
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.upcomingEventsTableView beginUpdates];
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.upcomingEventsTableView endUpdates];
}
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.upcomingEventsTableView; //creating a temporary placeholder;
switch (type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObjects:newIndexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:{
Event *changeEvent = [self.fetchedResultsController objectAtIndexPath:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.text = changeEvent.eventName;
break;
}
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
#end
An alternative to 2 table views is 2 sections in one table view. This will not work if you want each set of data to be split into sections, but in the code you posted, you don't seem to be doing that.
You create one FRC for each section, and for whichever FRC represents section 1 (second section), you have to adjust indexPaths when moving between the FRC and the table view. It's not all that difficult to manage.
This line:
if (upcomingEventsTableView){
will always return true, because you're just testing a pointer exists, not what it actually points to. So your delegate method is always returning the same items to both tables. The code in each option is the same at the moment anyway, but still...
You should really separate your concerns and have 2 view controllers, 1 for each table view.
You can do this using a single class that you instantiate twice and provide each with a predicate. That predicate allows the class to find the subset of data it should display, and in this case it's based on the date. The first one gets a predicate < nowDate and the second >= nowDate.
Each now has their own FRC instance which will return the correct items for it to display and everything is nicely compartmentalised and reusable.

+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name

What I'm trying to do: Get an NSFetchedResultsController to work for the first time to connect a UITableView to Core Data. I'm a newbie and really inexperienced, and I've followed Ray Wenderlich's Tutorial word to word.
The error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Medicine''`.
What I tried: None of the other StackOverflow questions that I found (including
this and this) have the an explanation, except for the second one which makes some sense. However, the method he mentioned is already written in my app delegate, which I imported in the TableViewController class I'm using.
What I'd greatly appreciate: An explanation of what I'm doing/what's gone wrong and how to fix it, rather than just the answer/correction.
PMMedicinesTableViewController.m
#import "PMMedicinesTableViewController.h"
#import "PMAppDelegate.h"
#import "Medicine.h"
#interface PMMedicinesTableViewController ()
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#end
#implementation PMMedicinesTableViewController
#synthesize fetchedResultsController = _fetchedResultsController;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (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;
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Medicine" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"name" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (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 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id sectionInfo =
[[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Medicine *med = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = med.name;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#",
med.name];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"medicineCellIdentifier" forIndexPath:indexPath];
// Set up the cell...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Thanks in advance!
The error is telling you that self.managedObjectContext is nil, which means that you haven't assigned a value to that property. Most likely you should have done this in whatever code created or loaded this view controller (e.g. in prepareForSegue:sender: if you're using storyboards).

Core Data to populate UITableView: can't get a NSString

my app stores players's data and I want to populate a UITableView with it, but I'm afraid I'm a bit lost.
Here's my code:
ShowResults.h
#import <UIKit/UIKit.h>
#interface ShowResults : UITableViewController<UITableViewDelegate,UITableViewDataSource, NSFetchedResultsControllerDelegate>
{
NSManagedObjectContext *managedObjectContext;
NSFetchedResultsController *fetchedResulstController;
NSArray *fetchedObjects;
}
#property (nonatomic) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic) NSManagedObjectContext *managedObjectContext;
#property (nonatomic,strong) NSArray *fetchedObjects;
#end
ShowResults.m
#import "ShowResults.h"
#import "F1AppDelegate.h"
#interface ShowResults ()
#end
#implementation ShowResults
#synthesize managedObjectContext;
#synthesize fetchedResultsController;
#synthesize fetchedObjects;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
F1AppDelegate *appDelegate =[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context =[appDelegate managedObjectContext];
NSError *error;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] &&[managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#,%#",error, [error userInfo]);
abort();
}
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Players" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// Test reading core data
for (NSManagedObject *player in fetchedObjects) {
NSLog(#"Name: %#", [player valueForKey:#"name"]);
NSLog(#"Surname: %#", [player valueForKey:#"surname"]);
NSLog(#"Address: %#", [player valueForKey:#"address"]);
NSLog(#"Email: %#", [player valueForKey:#"email"]);
NSLog(#"Phone: %#", [player valueForKey:#"phone"]);
NSLog(#"City: %#", [player valueForKey:#"city"]);
NSLog(#"Country: %#", [player valueForKey:#"country"]);
NSLog(#"Store: %#", [player valueForKey:#"store"]);
NSLog(#"Age: %#", [player valueForKey:#"age"]);
NSLog(#"Gender: %#", [player valueForKey:#"gender"]);
NSLog(#"Time: %#", [player valueForKey:#"time"]);
}
// End test
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSInteger rows = [fetchedObjects count];
return rows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
NSString *pieceOfData =[NSString stringWithFormat:#"%#", [fetchedObjects objectAtIndex:indexPath.row]];
cell.textLabel.font = [UIFont fontWithName:#"Helvetica" size:14];
cell.textLabel.text = pieceOfData;
NSLog(#"cellContent: %#",pieceOfData);
return cell;
}
#end
What I get on console output is
Name: Otto
Surname: Von Bismarck
Address: Schuhe, 3
Email: otto#munchen.de
Phone: +34988556633
City: MÜNCHEN
Country: GERMANY
Store: MÜNCHEN
Age: 44
Gender: Male
Time: 03:01:00
cellContent: <Players: 0x6b8f3c0> (entity: Players; id: 0x6b8b450 <x-coredata://C61C85CA-EB53-4B88-87AF-CC45EABFF8ED/Players/p1> ; data: {
address = "Schuhe, 3";
age = 44;
city = "M\U00dcNCHEN";
country = GERMANY;
email = "otto#munchen.de";
gender = Male;
name = Otto;
phone = "+34988556633";
store = "M\U00dcNCHEN";
surname = "Von Bismarck";
time = "03:01:00";
})
Can you help me to write these values into the tableView cells?
Thanks !
It looks like you are just need to do something like this:
// in your cellForRowAtIndexPath:
Players *player = [fetchedObjects objectAtIndex:indexPath.row];
// switch email for whatever other property you want to display
cell.textLabel.text = player.email;
It also looks like you might need to import Players.h into your class.

NSCFString objectAtIndex unrecognized selector sent to instance 0x5d52d70

I'm new to iPhone development, and been having a hard time trying to figure out why my table isn't working. It could be something with Core Data, I'm not sure. The viewDidLoad method works fine at the start, but when I try to scroll the table view, when a next row appears, I get the error:
NSInvalidArgumentException', reason: '-[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x5d52d70'
my View Controller.h:
#import <UIKit/UIKit.h>
#define kTableViewRowHeight 66
#interface RostersViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource> {
NSArray *objects;
}
#property(nonatomic,retain) NSArray *objects;
#end
my View Controller.m:
#import "RostersViewController.h"
#import "CogoAppDelegate.h"
#import "TeamCell.h"
#implementation RostersViewController
#synthesize objects;
- (void)viewDidLoad {
CogoAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Teams" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
objects = [context executeFetchRequest:request error:&error];
if (objects == nil)
{
NSLog(#"There was an error!");
// Do whatever error handling is appropriate
}
[request release];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotification
object:app];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[objects release];
[super dealloc];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [objects count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
id currObj = [objects objectAtIndex:indexPath.row];
cell.textLabel.text = [currObj valueForKey:#"name"];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return kTableViewRowHeight;
}
#end
Thanks for any help. It's very much appreciated.
In viewDidLoad change objects = [context executeFetchRequest:request error:&error]; to self.objects = [context executeFetchRequest:request error:&error];
executeFetchRequest returns an autoreleased object, which you are then storing directly to the ivar, this turns into a garbage pointer at a later point. Which just so happens to end up pointing to a string.
Using self.objects makes it use the synthesized setter and will retain it.