Detect updates on sectionNameKeyPath from NSFetchedResultsController - objective-c

Here is the case. I have 2 models:
Sport (with attribute name and relationShip players)
Player (with attribute name and relationShip sport)
In a ViewController, I wan't to display all players in a UITableView ordered by sport.
When a player name changes, NSFetchedResultsControllerDelegate callbacks are called.
But when the name of a Sport is updated, no NSFetchedResultsControllerDelegate callbacks are called.
Is this a normal behaviour ? How can I know the updates of Sport.name (without creating another NSFetchedResultsController) ?
Thank you and Best Regards.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"sport.name != NULL"];
[fetchRequest setPredicate:predicate];
NSArray *sortDescriptors = [NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"sport.name" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES],
nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Player"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entityDescription];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"sport.name"
cacheName:nil];
[fetchRequest release];
fetchedResultsController.delegate = self;
NSError *error = nil;
if ([fetchedResultsController performFetch:&error] == NO) {
ALog(#"Fetch error: %#", [error localizedDescription]);
}

You can using a tip:
If (fetchresultcontroller != nil || isOtherEnthityUpdate)
{
return fetchresultcontroller;
}
And do self.tableview reload from any place where u check other entity. It can be notification center or other tableview.

Related

Core Data crash: Keypath Project not found in entity

- (void)fetchResult
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"Project" ascending:YES];
[request setEntity:self.entityDescription];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
self.fetchResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
NSError *fetchError = nil;
BOOL success = [self.fetchResultController performFetch:&fetchError];
if (success) {
NSLog(#"fetched!");
}
else {
NSLog(#"fetch fail!");
}
}
The entityDescription and the context is set in the viewDidLoad method, as it is shown below:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = appDelegate.managedObjectContext;
self.entityDescription = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:self.managedObjectContext];
Crash info:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath Project not found in entity '
Could anybody give a hand?;)
The key used in the sort descriptor
[NSSortDescriptor sortDescriptorWithKey:#"Project" ascending:YES];
must be a property of your Core Data entity, not the name of the entity itself.
So you should replace #"Project" in the sort descriptor by some property of
the Project entity, such as #"name".
It is not related to the initial question, but it is highly related to the title of the question.
And as it was the only one I've found for this topic, it may be useful for someone else:
So, I've got "Core Data crash: 'keypath Videos not found in entity ...'" because I've used string interpolation in a predicate:
let projectName = "Videos"
let predicate = NSPredicate(format: "\(#keyPath(Scene.relatedProject.name)) == \(projectName)")
The correct way is:
let projectName = "Videos"
let predicate = NSPredicate(format: "\(#keyPath(Scene.relatedProject.name)) == %#", projectName)
Hope, it will save someone hour(s) of figuring out what is going on.
make sure you are using below syntax for your Query
Example:
fetchRequest.predicate = NSPredicate(format: "username = %#", userName)
where username is the key and userName is variable to hold any name in the example.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:predicate];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:context];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"Column_Name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *arrTemp = [context executeFetchRequest:fetchRequest error:&error];
if([arrTemp count]>0){
NSLog(#"%#",arrTemp);
}
Set cache name like:
self.fetchResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"temp"];

Is this the correct use of NSFetchedResultsController?

Is this is correct us of NSFetchedResultsController's getter method with the most modern Object-C and LLVM compiler that doesn't require synthesizing properties?
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController) {
return _fetchedResultsController;
}
if (!self.managedObjectContext) {
self.managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Session" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate: self.predicate];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController= [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return aFetchedResultsController;
}
In other parts of my code, I have this instead:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Set" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"sets == %#", self.exercise]];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return self.fetchedResultsController;
}
In your first example, you never assign anything to the property or ivar, so the lazy loading will occur every time the property is accessed. This isn't right.
In your second example you assign to the property, (thereby triggering any side effects of the property setter) and also return the value of the property (which wouldn't take many errors to leave you with an infinite loop). It may work now but it would be easy to break with a careless update.
It would be cleaner to assign to the internal ivar and return that.
So, at the end of your first example:
_fetchedResultsController = aFetchedResultsController;
return _fetchedResultsController;

Using an NSPredicate to access self.items

For various reasons (summarized at the end) I am experimenting with using an NSFetchedResultsController to return some NSManagedObjects.
Specifically, a Person has many Cars, modeled using the core data relationship cars. I want to add another method to Person to return the same cars as self.cars, but using a FRC.
I think I am making a basic error with my NSPredicate, which is designed to only find the cars where car.person == self:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Car"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dateAdded" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person == '%#'", self];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
BOOL success = [controller performFetch:&error];
No cars are being returned at all. Is my predicate incorrect?
Side Note - why not just use self.cars?
Like I say this is an experiment. I am hoping to benefit from the persistent cache of an NSFetchedResultsController.
The single quotes in the predicate are wrong, it should be
[NSPredicate predicateWithFormat:#"person == %#", self];
In the example you are fetching "Person" objects. Maybe you want to fetch "Car" objects instead?
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Car"];

iOS: How to Sort with NSSortDescriptor a value from relationship?

I have two objects with a relationship in my Data Model...
Person {Name, position, Title}
Position {position, PositionSort}
Each of the above have their own class files (subclasses of NSManagedObject). I'm trying to sort by PositionSort above. I can currently sort by Position by using the following code.
NSSortDescriptor *sortDescriptorPosition = [[NSSortDescriptor alloc] initWithKey:#"position" ascending:YES];
I am unsure though how I'm able to access and use the PositionSort. Any ideas? Thanks!
UPDATE: Added full function...
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptorState = [[NSSortDescriptor alloc] initWithKey:#"position.positionSort" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptorState, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:#"(companyID == %#)", company.companyID];
[fetchRequest setPredicate:searchPredicate];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"position.positionDescription" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
With the above code I'm getting the following error at Runtime.
2012-08-12 10:29:45.499 College[269:707] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0xd6bd310> valueForUndefinedKey:]: this class is not key value coding-compliant for the key positionDescription.'
I'm not sure you want to sort exactly, but the following NSSortDescriptor can be used to sort the Person objects using the PositionSort value.
NSSortDescriptor *sortDescriptorPosition = [[NSSortDescriptor alloc] initWithKey:#"position.PositionSort" ascending:YES];
perhaps, you looked for this...

displaying in a UITableView CoreData objects ordered in a Set

I have difficulties displaying objects coming from CoreData in a tableView.
I have 2 sorts of entities : Sample and SampleList. What is important to know is that a SampleList has an attribute sampleSet which is a set of samples (entity of Sample)
First I succeeded in displaying every SampleList. Here is viewDidLoad:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"SampleList" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastSampleDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle error
}
[self setSampleListArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
Once I click on a row in my tableView, I would like to display in another UITableView every sample from the SampleList selected.
I thought that I could pass to the subview SampleList mySampleList. But then, I don't know what to do with it as it is not organized.
How can I return an ordered array of Sample (ordered by dateSample attribute for example) ?
Thank you for your time !
You can just use NSSet and NSArray methods on sampleSet to get an ordered array:
sortedArray = [[sampleSet allObjects] sortedArrayUsingSelector:#selector(compare:)];
or if you want to specify particular sort descriptors instead of the regular "compare" method:
sortedArray = [[sampleSet allObjects] sortedArrayUsingDescriptors:sortDescriptors];