Changed sectionNameKeyPath attribute value is not reflected on NSFetchedResultsController - core-data

I have a table-view backed by an NSFetchedResultsController.
Any time an object in the underlying context changes, the NSFetchedResultsController automatically reflects new attribute values on the table-view. Yay.
The one exception I have noticed is that updates to the attribute value used for sectionNameKeyPath are not reflected automatically.
I'm guessing the attribute value used for the sectionNameKeyPath is so fundamental to the NSFetchedResultsController that I'll need to performFetch & reload the table-view again?
Update: heres the code used to configure the fetch request
- (void)configureFetch {
CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
NSFetchRequest *request =
[NSFetchRequest fetchRequestWithEntityName:#"Item"];
request.sortDescriptors =
[NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"locationAtHome.storedIn"
ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES],
nil];
[request setFetchBatchSize:15];
self.frc =
[[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:cdh.context
sectionNameKeyPath:#"locationAtHome.storedIn"
cacheName:nil];
self.frc.delegate = self;
}
and the code to perform the fetch:
- (void)performFetch {
if (self.frc) {
[self.frc.managedObjectContext performBlockAndWait:^{
NSError *error;
[self.frc performFetch:&error];
if (error) NSLog(#"%# '%#' %# (Reason: %#)",
self.class, NSStringFromSelector(_cmd),
error.localizedDescription, error.localizedFailureReason);
[self.tableView reloadData];
}];
}
}

The problem is not that the attribute used as sectionNameKeyPath is changed, but that an attribute in a related object is changed.
A fetched results controller tracks only changes to the fetched objects itself (the Item objects in your case), but not to related objects.
The only solution (as far as I know), is to add the section as an additional attribute to the Item entity, even if that means that you have duplicate data.

Related

Saving array of images in core data as transformable

I want to add my imageArray into coredata as transformable but this is not storing properly.
My save button coding.
- (IBAction)saveButton:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"FoodInfo" inManagedObjectContext:context];
NSAttributeDescription *messageType = [[NSAttributeDescription alloc] init];
[messageType setName:#"photos"];
[messageType setAttributeType:NSTransformableAttributeType];
[imagesForShow addObject:messageType];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unable to save context for class" );
} else {
NSLog(#"saved all records!");
[context save:nil];
}
//[newEntry setValue:imagesForShow forKey:#"images"];
}
Here 'imagesForShow' is my array of images.
When iam going to fetch this image array , this showing nil
- (void)viewDidLoad {
[super viewDidLoad];
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:#"FoodInfo"];
[request setReturnsObjectsAsFaults:NO];
arrayForPhotos = [[NSMutableArray alloc]initWithArray:[context executeFetchRequest:request error:nil]];
// Do any additional setup after loading the view.
}
What I am doing wrong with this code. Thanks.
In your save code:
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newEntry = [NSEntityDescription
insertNewObjectForEntityForName:#"FoodInfo"
inManagedObjectContext:context];
NSAttributeDescription *messageType = [[NSAttributeDescription alloc] init];
[messageType setName:#"photos"];
[messageType setAttributeType:NSTransformableAttributeType];
[imagesForShow addObject:messageType];
I can't even figure out what this is supposed to do. It's completely wrong. You should never be allocating an instance of NSAttributeDescription unless you are constructing a Core Data model on the fly-- which you are not doing and which almost nobody ever does. Creating the new entry is OK. The rest, I don't know. You said that imagesForShow is your array of images, so I don't know why you're also adding an attribute description to the array.
In a more general sense, if newEntry has a transformable attribute named photos and imagesForShow is an NSArray of UIImage objects, then you could do this:
[newEntry setValue:imagesForShow forKey:#"photos"];
This is similar to a line that you have commented out, though it's not clear why it's commented out.
But whatever you do get rid of the code creating the NSAttributeDescription.

NSFetchedResultsController with NSDictionaryResultsType in NSFetchRequest

I have been looking over this issue since a week and haven't got any solution, so I thought to make the question more generalized, may be it will help the users to look into it and give me a solution.
Scenario:
I have an expense tracking iOS Application and I have a view controller called "DashBoardViewController" (table view controller - with FRC) which would basically categorize my expenses/incomes for a given week, a month, or year and display it as the section header title for example : (Oct 1- Oct 7, 2012) and it shows expenses/incomes ROWS and related stuff according to that particular week or month or year.
My Question:
What I want to accomplish is :
Show Distinct Results based on "Category" attribute of "Money" entity and calculate "Sum" based on the attribute.
But, my view controller called "dashboard view controller" is filled with NSFetchedResultsController with section name key path as "type" that can be an expense or an income. In order to get Distinct results, I shall use Result type as NSDictionaryResultsType in my fetch request which will give me unique results but FRC fails, it doesn't work with that. So, how will I get my section names then? I have posted the code below.
EDIT - BASED ON MARTIN'S SUGGESTION
- (void)userDidSelectStartDate:(NSDate *)startDate andEndDate:(NSDate *)endDate
{
AppDelegate * applicationDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext * context = [applicationDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Money" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicateDate = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#)", startDate, endDate];
[fetchRequest setPredicate:predicateDate];
// Edit the sort key as appropriate.
typeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"type" ascending:YES];
dateSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
if(self.choiceSegmentedControl.selectedIndex == 0)
{
choiceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"cat" ascending:NO];
}
if(self.choiceSegmentedControl.selectedIndex == 1)
{
choiceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"vendor" ascending:NO];
}
if(self.choiceSegmentedControl.selectedIndex == 2)
{
choiceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"paidBy" ascending:NO];
}
NSArray * descriptors = [NSArray arrayWithObjects:typeSortDescriptor, dateSortDescriptor,choiceSortDescriptor, nil];
[fetchRequest setSortDescriptors:descriptors];
[fetchRequest setIncludesSubentities:YES];
NSError * anyError = nil;
NSArray *propertiesToFetch = [[NSArray alloc] initWithObjects:
[entity.propertiesByName objectForKey:#"cat"],
nil];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:propertiesToFetch];
NSArray * objects = [context executeFetchRequest:fetchRequest error:&anyError];
for (NSDictionary *d in objects)
{
NSLog(#"NSDictionary = %#", d);
}
NSSet *uniqueSet = [NSSet setWithArray:[objects valueForKey:#"cat"]];
uniqueArray = [NSMutableArray arrayWithArray:[uniqueSet allObjects]];
self.categoryArray = uniqueArray;
if(_fetchedResultsController)
{
[_fetchedResultsController release]; _fetchedResultsController = nil;
}
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"type" cacheName:nil];
//self.fetchedResultsController.delegate = self; in order to stop "change tracking"
if(![_fetchedResultsController performFetch:&anyError])
{
NSLog(#"error fetching:%#", anyError);
}
[fetchRequest release];
//Finally you tell the tableView to reload it's data, it will then ask your NEW FRC for the new data
[self.dashBoardTblView reloadData];
self.startDate = startDate;
self.endDate = endDate;
}
With this code, SECTION NAME KEY PATH is not working and it's complaining that the object will be placed in unnamed section.
A fetched results controller does not support change tracking (i.e. the FRC delegate is set) in combination with a fetch request with NSDictionaryResultType.
The reason is documented with the setIncludesPendingChanges: function:
Special Considerations
A value of YES is not supported in conjunction
with the result type NSDictionaryResultType, including calculation of
aggregate results (such as max and min). For dictionaries, the array
returned from the fetch reflects the current state in the persistent
store, and does not take into account any pending changes, insertions,
or deletions in the context.
Change tracking of the FRC implies includesPendingChanges = YES for the fetch request, and that does not work with NSDictionaryResultType.
One workaround could be to use a FRC without change tracking, so you do not set the FRC delegate. But that means that to update your table view, you have to
save the managed object context, and
call performFetch on the FRC and reloadData on the table view.
Another workaround could be to use the FRC to fetch all sections and rows without the sum aggregation, and use the results to compute new table rows with the aggregation in memory (e.g. in controllerDidChangeContent).
UPDATE: (From the discussion) Another important point is that if you use a fetch request with NSDictionaryResultType, then the sectionNameKeyPath of the fetched results controller must be included in the propertiesToFetch of the fetch request.

Core Data NSMutable Set trouble

Ok I am trying to grab a NSMutable Set. Yes I have a previous post on this but this is slightly different. I have A player entity and a team entity. It is set up as a one to many relationship... On a different view controller I added players to the team. Now I am trying to get that teams players to show up on a table view... I am fetching the information as follows.
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSString *entityName = #"Team";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = delegate.managedObjectContext;
// 4 - Sort it
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"players"
ascending:NO
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Then on my cell for row at index path I am setting the Player object to the fetched results as follows
Player *p = [_fetchedResultsController objectAtIndexPath:indexPath];
Then, I am setting the title of the cell like so.
cell.textLabel = p.firstName;
I am getting the error
reason: 'to-many key not allowed her
I am wondering what am I doing wrong???
Figured it out! I was sorting on a one to many relationship which is a NO NO in Core Data. I switched to sort on the player object and added a predicate to find the proper players I needed.

ios5 core data: nsfetchresultcontroller refresh uitable

i'm working on an app with core data with storyboard. the app has uitabbarcontroller as rootview. i have created entity and generated the classes. each tab has it own uinavigation controller. the view in the tab 1 just saves some data in the database from uilabels. and it works fine and data is in the database.
the view in tab 2 displays the data from the database in uitableview. the data is only shown when i kill the app and restart it. so the ui table doesnt get refreshed.
first method: i have passed the managedobject context from the app delegate to the both views. so ui table doesnt get refreshed till kill and restart.
second method: i (mis)used the app delegate, but still the same result.
MyApplicationDelegate *appDelegate = (MyApplicationDelegate *)[[UIApplication sharedApplication] delegate];
how can one achieve that one view only adds data to core data(which it does right now) and the second view get notified of changes and display it in uitableview?
edit
-(NSFetchedResultsController *) fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Favis" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"chapterid" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[NSFetchedResultsController deleteCacheWithName:#"Master"];
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;
}
it is the code when you use the core data template. only tweaked to work with my app. i have it in my both viewcontroller.
edit 2
i have implemented nsfetchedresultcontroller in my uitableview controller.
the manged object returns the exact number of data in the database, but ui table doesnt get refreshed. i also did [self.tableview reloaddata] but no luck
In the viewController with the UITableVIew implement the methods for the NSFetchedResultsControllerDelegate. The documentation has the full implementation of those methods.
And then make your viewController the delegate of the NSFetchedResultsController fetchedResultsController.delegate = self;
There should be some thing in 2 tab as to notify as data changed in database update the new data, Is there any? If
NSManagedObjectContext, NSFetchedResultsController in 2 tab by saying
Implement NSFetchedResultsController delegation methods.
in appdelegate
secTab.managedObjectContext = self.managedObjectContext;
Surely it works now

NSFetchResultsController + sectionNameKeyPath + section order

I have a one to many entity relationship between two entities:
EntityP (Parent) <-->> EntityC (Child)
Attributes and Relationships:
EntityP.title
EntityP.dateTimeStamp
EntityP.PtoC (relationship)
EntityC.title
EntityC.dateTimeStamp
EntityC.CtoP (relationship) // Can be used to get "one" EntityP record
I use fetch results controller to show the results. Here's my implementation of the fetch results controller:
#pragma mark -
#pragma mark Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create the fetch request for the entity
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Set Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityC" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set Predicate
// (Ignore, we want to get list of all EntityC records)
// Set Sort Descriptors (sort on Parent - for section, and then on Child - for rows)
NSSortDescriptor *sortDescriptorPDate = [[NSSortDescriptor alloc] initWithKey:#"CtoP.dateTimeStamp" ascending:YES];
NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:#"dateTimeStamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorPDate, sortDescriptorDate, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:20];
// Create and initialize the fetch results controller
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"CtoP.title" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
// Cleanup memory
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptorPDate release];
[sortDescriptorDate release];
[sortDescriptors release];
NSError *error = nil;
if (![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;
}
Now, for example, if i have following data in the persistence store:
EntityP.dateTimeStamp EntityP.title EntityC.dateTimeStamp EntityC.title
Today B Today d
Yesterday A Yesterday a
Today B Yesterday c
Yesterday A Today b
Note: Yesterday and Today is in NSDate format.
Then i should get the sections and rows in following order (exactly):
A
a
b
B
c
d
But, the sort is not working like this. I'm getting the rows in correct order, but the sections are not ordered! I hope sortDescriptorPDate is doing his job. What am i missing? Thanking in anticipation.
Not sure I understand your setup but...
By default the section titles are the capitalized first letter of the section name. If you want a custom sections such as ones based on dates, you will need to subclass NSFetchedResultsController and override the various sectionIndex... methods to return the correct sections, section indexes and section titles.
This seems to be working now! I'm not sure what changed! – Mustafa Sep 6 '10 at 4:03

Resources