NSFetchedResultsController Creating Sections - iphone

When using the NSFetchedResultsController to create section headers for the UITableViewController the fetchedResultsController.sections has an object for each item not each section.
//Set up the request
NSManagedObjectContext *context = self.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Person"
inManagedObjectContext:context]];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = nil;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName"
ascending:YES];
NSArray *sortDescriptors = nil;
sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[sortDescriptor release]; sortDescriptor = nil;
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release]; sortDescriptors = nil;
//setup fetch results controller
NSFetchedResultsController *controller = nil;
controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"firstName"
cacheName:#"PersonCache"];
__fetchedResultsController = controller;
[fetchRequest release]; fetchRequest = nil;
//IMPORTANT: Delete cache before changing predicate
[NSFetchedResultsController deleteCacheWithName:nil];
NSError *error = nil;
if (![controller performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
} else if ([[controller fetchedObjects] count] == 0){
[self retrievePeoples];
}
NSLog(#"result count: %i", [[controller fetchedObjects] count]);
NSLog(#"section count: %i", [[controller sections] count]);
NSLog(#"sectionIndexTitles count: %i", [[controller sectionIndexTitles] count]);
This returns:
result count: 18
section count: 18
sectionIndexTitles count: 13
Shouldn't the section count and the sectionIndexTitles count match? When the numberOfSectionsInTableView: and tableView:numberOfRowsInSection: methods are called I should just be able to look to the fetchedResultsController.section for a count without any additional sorting.
How do I setup the NSFetchResultsController properly to have each object in the sections array be for per section and not for all objects?

This was answered by Phillip Mills on another forum. The problem was I was using the entire firstName to create sections (not just the first letter). The fix is to update the entity to create the a section title whenever updated or changed. Apple's DateSectionTitles has a sample of what to do.

Related

NSFetchResultsController delegate not getting called

I have the following code:
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DiskStory" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"isRemoved == %#", [NSNumber numberWithBool:NO]];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Set limit
[fetchRequest setFetchBatchSize:25];
// Set batch size
[fetchRequest setFetchLimit:50];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:kSavedStoryCache];
aFetchedResultsController.delegate = self;
fetchedResultsController = aFetchedResultsController;
}
return fetchedResultsController;
}
in my viewDidLoad I have:
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
I was wondering why my :
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
}
delegate, isn't called? I put a break point inside it. Any idea?
I had this problem before, it's not called when you call performFetch for the first time but only when data in fetchedResultsController is actually changed. For instance when you delete object from context that is in this fetch results, delegate will be called.
I think you have used self. at wrong places. Please see the code below; i have added comments to the changes
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DiskStory" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"isRemoved == %#", [NSNumber numberWithBool:NO]];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Set limit
[fetchRequest setFetchBatchSize:25];
// Set batch size
[fetchRequest setFetchLimit:50];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:kSavedStoryCache];
aFetchedResultsController.delegate = self;
fetchedResultsController = aFetchedResultsController; // -> Shouldn't be self.fetchedResultsController
}
return fetchedResultsController; // -> Shouldn't be self.fetchedResultsController
}
Also, if you have synthesize your fetchedResultsController as follows:
fetchedResultsController = _fetchedResultsController
Then in - (NSFetchedResultsController *)fetchedResultsController method you should refer to it as _fetchedResultsController

Using Search bar with Core Data

I have tried various methods and this is the closest I have gotten, but the updating of the table makes all the entries off screen blank, and when I scroll, they all blank out. I am still new and am not entirely sure about implementing a search on core data tableview at all.
If I don't nil things out it dies here:
UPDATE: this is what I am trying now, and I am getting the same crash:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no section at index 1'
`Recipe *recipe = (Recipe *)[fetchedResultsController objectAtIndexPath:indexPath];` **`Thread 1 received signal SIGABRT`**
#pragma mark -
#pragma mark search bar methods
- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
NSLog(#"searched");
fetchedResultsController = nil;
//============
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipe" inManagedObjectContext:managedObjectContext];
if (searchBar.text !=nil) {
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"name contains[cd] %#", searchBar.text];
[fetchedResultsController.fetchRequest setPredicate:predicate];
} else {
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"All"];
[fetchedResultsController.fetchRequest setPredicate:predicate];
}
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"state" ascending:YES];// was name
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,sortDescriptor2, nil];// was 2
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"state" cacheName:#"Root"];//#"state"
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptor2 release];
[sortDescriptors release];
//==============
// dismiss the search keyboard
[searchBar resignFirstResponder];
// reload the table view
//[self.tableView reloadData];
}
- (void)configureCell:(RecipeTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// Configure the cell //DIES HERE
Recipe *recipe = (Recipe *)[fetchedResultsController objectAtIndexPath:indexPath];
cell.recipe = recipe;
}
In other conditions, it dies when I try to move the table up or down.
Thanks for any guidance or help with understanding and solving this issue!
Rob
tableViewController.m
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipe" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"state" ascending:YES];// was name
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,sortDescriptor2, nil];// was 2
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"state" cacheName:#"Root"];//#"state"
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptor2 release];
[sortDescriptors release];
}
return fetchedResultsController;
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
self.fetchedResultsController = nil;
self.searchBar.text=#"";
[self.searchBar setShowsCancelButton:NO animated:YES];
[self.searchBar resignFirstResponder];
//self.tableView.allowsSelection = YES;
//self.tableView.scrollEnabled = YES;
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
// added 2 below
//self.fetchedResultsController.delegate = nil;
self.fetchedResultsController = nil;
[self.searchBar setShowsCancelButton:YES animated:YES];
//self.tableView.allowsSelection = NO;
//self.tableView.scrollEnabled = NO;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
// added 2 below
//self.fetchedResultsController.delegate = nil;
self.fetchedResultsController = nil;
NSLog(#"fetchObjects");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipe" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
//NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"state" ascending:YES];// was name
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
//NSLog(#"NSInteger value :%#", sortDescriptor);
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// perform query
NSString *query = self.searchBar.text;
if (query && query.length) fetchRequest.predicate = [NSPredicate predicateWithFormat:#"name CONTAINS[cd] %#", query];
// Edit the section name key path and cache name if appropriate.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"state" cacheName:#"Root"];//#"state"
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
//[sortDescriptor release];
[sortDescriptor2 release];
[sortDescriptors release];
//[self.tableView reloadData];
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
It doesn't look like you don't ever actually perform the fetch request. From the docs:
After creating an instance, you invoke performFetch: to actually
execute the fetch.
Are you using a UISearchDisplayController? if you are you end up with two tables: one that displays your normal stuff and the other related to the search. You have to treat the tables separately.
There is a writeup of all the code you need to implement searching on tables at How to filter NSFetchedResultsController (CoreData) with UISearchDisplayController/UISearchBar

NSFetchedResultsController refresh refetch?

I want to refetch data from my NSFetchedResultsController using a different predicate which is set using a boolean value. How do I refresh NSFetchedResultsController to fetch a new set of data?
- (void)refreshFetchedResultsController {
NSLog(#"refreshFetchedResultsController");
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(#"Error loading data",
#"Error loading data")
message:[NSString stringWithFormat:
NSLocalizedString(#"Error was: %#, quitting.", #"Error was: %#, quitting."),
[error localizedDescription]]
delegate:self
cancelButtonTitle:NSLocalizedString(#"Cancel", #"Cancel")
otherButtonTitles:nil];
[alert show];
}
}
which calls
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
NSLog(#"i was executed.");
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
TapAppDelegate *appDelegate = (TapAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Favorite" inManagedObjectContext:managedObjectContext];
NSString *sectionKey = #"favname";
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"favname" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
if(showAll == NO){
if(isXSelected == NO){
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isFirst == TRUE"];
[fetchRequest setPredicate:predicate];
}
if(isXSelected == YES){
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isFirst == FALSE"];
[fetchRequest setPredicate:predicate];
}
}
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:sectionKey
cacheName:nil];
[sortDescriptor release];
[sortDescriptors release];
frc.delegate = self;
_fetchedResultsController = frc;
[fetchRequest release];
return _fetchedResultsController;
}
Here's how we do it in an application:
// Assuming these exist
NSPredicate * predicate;
NSString * cacheName;
[[fetchedResultsController fetchRequest] setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:cacheName];
NSError * error = nil;
[fetchedResultsController performFetch:&error];
if (error) {
// report error
}
Don't forget to set fetchedResultsController = nil before performFetch. Otherwise it will use the old one.
I had similar problem, I couldn't figure out why my collection view doesn't refresh its cells.
Adding the method below fixed my problems.
-(void)didUpdateObjectAtIndexPath:(NSIndexPath *)indexPath{
UICollectionView *strongCollectionView = self.collectionView;
[strongCollectionView reloadItemsAtIndexPaths:#[indexPath]];
}

Core Data , NSFetchResultsController leaking

I am not sure if the leak is in my implementation or is it from apple's side....
Instruments indicate me that I have a leak in this line :
if (![[self fetchedResultsController]
performFetch:&error])
I am adding annotations by reading the fetchController to the Map.... like this :
-(void)fillMapWithAnnotations{
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
for(int a=0; a<[[[fetchedResultsController sections]objectAtIndex:0] numberOfObjects]; a++){
LookAround *look=(LookAround *)[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:a inSection:0]];
if(look){
AddAnnotation *newAnnotation=[[AddAnnotation alloc]initWithLookAround:look];
if(newAnnotation){
[self.mapView addAnnotation:newAnnotation];
[newAnnotation release];
newAnnotation=nil;
}
}
}
}
and I initialize my FetchController like this:
- (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:#"LookAround" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity: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:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
}
return fetchedResultsController;
}
I get a leak as soon as i Navigate Back, the ViewController gets Deallocated in which I release my fetch controller object.
The objects that leak are numerous (and of the same type I guess) around the number of records in my sqlite DB
Thanks in advance for your help....
As I noted above, the leak is probably in your AddAnnotation class.

How to properly configure NSFetchedResultsController

I am placing an NSFetchedResultsController into my code so I get that nice automatic sectioning of my table view data.
So I am running a test to make sure everything works properly. I have a single Book entity in my persistent store. I will first perform the fetch the old way, then I will try to use NSFetchedResultsController. The difference between the 2 blocks of code is just 2 lines.
Code without NSFetchedResultsController:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kBookEntityName inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
//The following 2 lines will be replaced by the NSFetchedResultsController
NSMutableArray *mutableFetchResults = [[[self.managedObjectContext executeFetchRequest:request error:nil] mutableCopy] autorelease];
Book *result = (Book*)[mutableFetchResults objectAtIndex:0];
NSString* title = [result valueForKey:#"title"];
NSString* priority = [result valueForKeyPath:#"priority.name"];
[request release];
Now I substitute in the lines for the NSFetchedResultsController:
NSFetchedResultsController* fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"title" cacheName:#"BookList"];
Book *result = (Book*)[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
Seems pretty cut and dry. The first code block properly fetches the single Book entity. The code with the NSFetchedResultsController, however, does not. Instead it returns nil.
My question is: Am I properly configuring the NSFetchedResultsController in this example?
(note, the fetchedObjects property of the NSFetchedResultsController is also nil)
I think you still need to tell the NSFetchedResultsController to actually perform the fetch:
NSError *error;
BOOL success = [controller performFetch:&error];
(taken from the example in the NSFetchedResultsController reference)
one other thing that seems odd: do you really want to use "title" as the sectionNameKeyPath? won't that basically create a separate section for each book?
You need to initialize your NSFetchedResultsController only once, as follows.The code assumes
NSFetchedResultsController *fetchedResultsController;
NSManagedObjectContext *managedObjectContext;
are declared in the header of your controller, and that managedObjectContext is already properly initialized.
- (void)viewDidLoad {
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
NSLog(#"%d objects fetched", [[fetchedResultsController fetchedObjects] count]);
}
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:kBookEntityName inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
/* Optional settings
[request setResultType:NSManagedObjectResultType];
[request setIncludesPropertyValues:YES];
[request setIncludesPendingChanges:NO];
[request setReturnsObjectsAsFaults:NO];
*/
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];;
// 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:request managedObjectContext:managedObjectContext sectionNameKeyPath:#"title" cacheName:#"BookList"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[request release];
return fetchedResultsController;
}