I'm trying to use NSFetchedResultsController to display data in a table view. My data model has an array of users, each user has an array of categories, each category has an array of organizations, and each organization has an array of accounts. The table view displays data from a single user. Each row represents an organization belonging to the user, and the organizations are separated into sections, with each section containing the organizations belonging to a specific category. In my app delegate I populate my application with some dummy data, and NSFetchedResultsController displays that data fine. The problem is that when I try to add a new organization to an existing category the row is added but it is added to a new section containing only that row, as opposed to merging with the section that holds the rest of the organizations for that category.
Here is the code for my NSFetchedResults controller:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"PKOrganization" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Tell the fetch request to only retrieve the organizations from the proper user
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(category.user.name = '%#')", self.user.name]]];
// Sort the data
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"category.name" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor1,sortDescriptor2];
[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:#"category" cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
It might also be worth noting that I am able to tell that the data is added as expected. It just doesn't get displayed correctly.
Try changing this line
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"category" cacheName:#"Master"];
To this:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"category.name" cacheName:#"Master"];
In my experience, I needed the sectionNameKeyPath to be exactly the same as the first sortDescriptor in order to get sections to work properly.
Related
I'm having difficulty creating tableView sections using a relationship.
I have two entities with a relationship List <----->> Item.
I want the List to be the sections and the Item to be the rows. I set the sectionNameKeyPath with a key path #"itemList".
And here's what the rest of my fetchedResultsController looks like
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
// Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Item"];
[fetchRequest setFetchBatchSize:20];
// Sort Descriptors
NSSortDescriptor *itemSort = [[NSSortDescriptor alloc] initWithKey:#"displayOrderItem" ascending:YES];
NSSortDescriptor *sectionSort = [[NSSortDescriptor alloc] initWithKey:#"displayOrderList" ascending:YES];
NSArray *sortDescriptors = #[sectionSort, itemSort];
[fetchRequest setSortDescriptors:sortDescriptors];
// Fetched Results Controller
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"itemList" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
return _fetchedResultsController;
}
The result is that the fetchedResultsController doesn't populate the tableView at all. When I try it without sections, with sectionNameKeyPath:nil and just setSortDescriptor:itemSort, it populates the tableView fine. Also, numberOfSectionsInTableView and controller didChangeSection is properly set up.
I'm not sure what I'm doing wrong. Can anybody help me with this?
Thanks
Change the section name key path to itemList.listName as the FRC is expecting a string name for the section, not a managed object 'representing' that section.
I have a strange bug: if I uncomment my NSPredicate, the resulting UITableView is empty.
My data Model is the following:
Category <-->> Feed <-->> Post
I am fetching the Posts. Post.feed is a Post's Feed. Feed has an rss NString property.
Here's the code:
- (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:#"Post"
inManagedObjectContext:_globalMOC];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"feed.rss == %#", _detailItem.rss];
[fetchRequest setPredicate:predicate];
// 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:_globalMOC
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate.
// You should not use this function in a shipping application, although it may be useful
// during development. If it is not possible to recover from the error, display an alert
// panel that instructs the user to quit the application by pressing the Home button.
//
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
return _fetchedResultsController;
}
I only see results if I uncomment the NSPredicate. I tried with LIKE, ==, =, with double and single quotes around %#...
The best would be to directly compare the Feed object...
Anyone can help me?
Solved here.
The problem was that the NSFetchedResultsController was initialized once before the _detailItem was even set.
I can't manage to sort a view-based tableview. I use an arrayController that control an entity of core data.
I tried to select a column and in the attribute inspector I used as a sort key the attributes name relative to that column and compare: as a selector... when I build and run I click on the header and now display the arrow that change every click, but the nothing happens with the rows. no sorting.
How can I fix it?
I think I'm missing something easy, but I can't get over it.
You may want to consider using an NSFetchedResultsController. From the Apple documentation for NSFetchedResultsController,
You use a fetched results controller to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
The code looks like this where the line that provides the sort descriptor starts with NSSortDescriptor.
- (NSFetchedResultsController *)fetchedResultsController
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"YourEntityName"
inManagedObjectContext:yourManagedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"YourSortKey"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *fetchedObjects = [yourManagedObjectContext executeFetchRequest:fetchRequest
error:&error];
if (fetchedObjects == nil) {
// Handle the error
}
yourFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
return yourFetchedResultsController;
}
Scenario :
I have an expense tracking iOS Application and I am storing expenses from a expense detail view controller into a table view that shows the list of expenses along with the category and amount.
On the top of the tableview, is a UIView with CALENDAR button, a UILabel text showing the date (for example: Oct 23, 2012 (Sun)) and 2 more buttons on the side.
The pressing of the calendar button opens up a custom calendar with the current date and the two buttons are for decrementing and incrementing the date correspondingly.
I want to save the expenses according to the date which is an attribute in my Core data entity "Expense".
Question: Suppose I press the calendar button and choose some random date from there, the table view underneath it, should show that day's particular expenses. What I mean is I want the table view to just show a particular date's expenses and if I press the button for incrementing the date or decrementing the date, the table view should show that day's expenses. I am using NSFetchedResultsController and Core Data in order to save my expenses.
Any thoughts on how I would achieve this? Here's the code for FRC.
-(NSFetchedResultsController *)fetchedResultsController
{
if(_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
AppDelegate * applicationDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext * context = [applicationDelegate managedObjectContext];
NSFetchRequest * request = [[NSFetchRequest alloc]init];
[request setEntity:[NSEntityDescription entityForName:#"Money" inManagedObjectContext:context]];
NSSortDescriptor *sortDescriptor1 =
[[NSSortDescriptor alloc] initWithKey:#"rowNumber"
ascending:YES];
NSArray * descriptors = [NSArray arrayWithObjects:sortDescriptor1, nil];
[request setSortDescriptors: descriptors];
[request setResultType: NSManagedObjectResultType];
[request setIncludesSubentities:YES];
[sortDescriptor1 release];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController.delegate = self;
[request release];
NSError *anyError = nil;
if(![_fetchedResultsController performFetch:&anyError])
{
NSLog(#"error fetching:%#", anyError);
}
return _fetchedResultsController;
}
Thanks guys.
You would have to create a new NSFetchedResultsController with a new NSFetchRequest that has an appropriately set NSPredicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date == %#)", dateToFilterFor];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
// ...
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"SomeCacheName"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
Don't forget to call [self.tableView reloadData]; after assigning the new FRC.
Edit:
You can assign a predicate to an NSFetchRequest which then is assigned to the fetchedResultsController. You can think of the predicate as a filter.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date == %#)", dateToFilterFor];
If you add this to the fetch request by calling [fetchRequest setPredicate:predicate]; you tell the fetched request to only fetch results where to date property of the NSManagedObject matches the date you provide in the predicate. Which is exactly what you want here.
So if you have a method that's called after the user selected a date you could modify it like this:
- (void)userDidSelectDate:(NSDate *)date
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//Here you create the predicate that filters the results to only show the ones with the selected date
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date == %#)", date];
[fetchRequest setPredicate:predicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[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;
//Here you replace the old FRC by this newly created
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//Finally you tell the tableView to reload it's data, it will then ask your NEW FRC for the new data
[self.tableView reloadData];
}
Notice that if you're not using ARC (which you should) you'd have to release the allocated objects appropriately.
I'm creating an app that lets a salesperson order stock for their customers from their iPhone.
The user navigates to a customer and creates an order. A blank tableview appears and the user then adds items to the tableview by selecting them from an inventory screen.
When they add an item to the order, the navigation controller pops the view and shows the order view again. The user should only be able to see orders for that customer.
I originally built the app entirely in sqlite and I achieved this by using the query
SELECT PRODUCT FROM TRANSLINE WHERE CUSTOMERACCNO = ?
I have now moved onto Core Data and I need to achieve the same functionality. I'm trying to implement this behaviour in the fetchedResultsController method using NSPredicate, but I can't seem to get it working - all I get is a blank screen. However, when I don't implement it, I get ALL orders i.e. orders for every customer, not just this one.
Here's my code :
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Configure the Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TransLine" inManagedObjectContext:__managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
//Configure the predicate
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"customerAccountNo == %#", _customerAccountNo];
[fetchRequest setPredicate:predicate];
//Configure Sort Descriptors
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"PRODAC" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// 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;
}
Thanks in advance for all your help.
Well, it seems that in my frustration.... I am, in fact, an idiot. Guess what I forgot to include.
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}