I have a Table View Controller with a list of Formations handled by a fetchResultsController.
Here's how my core data entities looks like :
I try to sort my fetchResultsController by dateRangelike this :
// |fetchedResultsController| custom setter
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:self.fetchRequest managedObjectContext:self.mainManagedObjectContext sectionNameKeyPath:#"dateRange" cacheName:kFormationsFetchedCacheName];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
// |fetchRequest| custom setter
- (NSFetchRequest *)fetchRequest {
if (_fetchRequest != nil) {
return _fetchRequest;
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"student == %#", self.currentStudent];
NSSortDescriptor *dateDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"dateRange" ascending:NO];
NSSortDescriptor *nameDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:NO];
_fetchRequest = [[NSFetchRequest alloc] initWithEntityName:kBSFormation];
_fetchRequest.predicate = predicate;
_fetchRequest.sortDescriptors = [NSArray arrayWithObjects: dateDescriptor, nameDescriptor, nil];
return _fetchRequest;
}
Everything is fine when i try to add the very first Formation, but for the next ones, i have these errors:
2013-01-30 22:43:08.370 [7202:c07] -[BSDateRange compare:]: unrecognized selector sent to instance 0x81781a0
2013-01-30 22:43:08.371 [7202:c07] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[BSDateRange compare:]: unrecognized selector sent to instance 0x81781a0 with userInfo (null)
2013-01-30 22:43:08.372 [7202:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BSDateRange compare:]: unrecognized selector sent to instance 0x81781a0'
If i comment this line : NSSortDescriptor *dateDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"dateRange" ascending:NO];, it's working but my table view is messy as the sectionNameKeyPath is set to dateRange
Someone is figuring out what's the problem here ? :/
You're telling it to sort based on the dateRange relationship. But dateRange is a relationship to BSDateRange, and how does Core Data compare those? Should it use from, or maybe to, or some combination of those? You can't just tell it to sort on an object like that, because it's not obvious how sorting should work.
Instead, first figure out what sorting even means. Then modify your sort descriptor appropriately. For example if you decide that sorting depends on the from value, change the sort descriptor to use the key path to from:
NSSortDescriptor *dateDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"dateRange.from"
ascending:NO];
If you need to sort based on both from and to, use more than one sort descriptor.
Related
I am trying to create a NSFetchedResultsController with a request to get photos from a specific region. I have two models Region and Photo in my core data. Below is my code of where I am getting the error:
- (void)setRegion:(Region *)region
{
_region = region;
// Making a request for the particular region
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
request.sortDescriptors = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(localizedStandardCompare:)];
request.predicate = [NSPredicate predicateWithFormat:#"fromRegion = %#", region];
NSLog(#"%#", region.name);
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:[region managedObjectContext]
sectionNameKeyPath:nil
cacheName:nil];
}
I am receiving the following errors:
-[NSSortDescriptor countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x8d67850
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSSortDescriptor countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x8d67850'
I don't understand why I am receiving it. All I am doing is retrieving photos from the database that contains a specific region. However, I am receiving as error on the self.fetchedResultsController line. This core data tableview controller that contains this code is actually the designation view controller from another core data tableview controller.
I have looked around for other solutions but they only deal with problems related to looping through the self.fetchedResultsController which I am not doing. I am only displaying the photos in a tableview from the fetchedResultsController.
The problem is here:
request.sortDescriptors = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(localizedStandardCompare:)];
Fetch requests use an array of sort descriptors, because you can provide more than one. That's why it's sortDescriptors instead of sortDescriptor. You need to provide an array. A simple fix is to change the line to create a one-element array containing your descriptor:
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(localizedStandardCompare:)]];
Additional evidence that this is the problem: countByEnumeratingWithState:objects:count: is a method that's defined by the NSFastEnumeration protocol. Since NSArray adopts that protocol, it would be expected to implement the method. But since NSSortDescriptor doesn't adopt that protocol, it would not have that method.
The following method:
- (NSMutableArray*) timeSortedBegins {
NSMutableArray* begins = [self.spans valueForKey: #"begin"];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey: #"cycleOffsetObject" ascending: YES];
[begins sortUsingDescriptors: #[sort]];
return begins;
}
throws this runtime exception:
2014-03-21 14:41:32.482 myValve[1741:60b] -[__NSArrayI sortUsingDescriptors:]: unrecognized selector sent to instance 0x16d7bc20
2014-03-21 14:41:32.484 myValve[1741:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI sortUsingDescriptors:]: unrecognized selector sent to instance 0x16d7bc20'
I have used breakpoints to convince myself that the begins array is indeed full of (two in this case) WEAnchor* objects. And that object implements the following two methods:
- (NSTimeInterval) cycleOffset {
return self.offset + (self.datum ? self.datum.cycleOffset : 0.0);
}
- (NSNumber*) cycleOffsetObject {
return [NSNumber numberWithDouble: self.cycleOffset];
}
To be honest, I only added the cycleOffsetObject wrapper method, because I thought maybe it couldn't work with non object values, I was using initWithKey: #"cycleOffset" before that. I have not declared these in the header file as a property, they're just accessor methods, not state. Is that the problem? If it is, how do you sort by the return value of a given selector? Or is it something head smackingly obvious that I'm just missing?
As #dtrotzjr says, it sounds like your array is an immutable, not a mutable array.
You can either use mutableCopy to create a mutable copy and then sort that copy, or use the NSArray method sortedArrayUsingDescriptors: (which operates on an immutable array, and returns a sorted version of the contents as a second immutable array.)
To use mutableCopy, your code might look like this:
- (NSMutableArray*) timeSortedBegins {
NSMutableArray* begins = [[self.spans valueForKey: #"begin"] mutableCopy];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey: #"cycleOffsetObject" ascending: YES];
[begins sortUsingDescriptors: #[sort]];
return begins;
}
Check that [self.spans valueForKey: #"begin"] is actually an NSMutableArray before casting it. The error message indicates that the pointer is actually an NSArray
This seems a fairly common workflow I have an issue with that I cannot figure why. I have a UITableView with NSFetchedResultsController supplying the rows. To add a new object, I insert into context and then present a new view controller to edit the details. The add button action is like so:
-(IBAction)addNewIssueType:(id)sender
{
IssueType *newUntitledType = [NSEntityDescription insertNewObjectForEntityForName:#"IssueType" inManagedObjectContext:self.managedObjectContext];
newUntitledType.name = NSLocalizedString(#"Untitled Task", #"Name for newly created task");
[self saveContext];
self.editingType = newUntitledType;
[self presentNameEditViewForType:newUntitledType];
}
I am crashing with exception at the saveContext method, error:
2013-05-11 16:25:35.990 App [18843:c07] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet. with userInfo (null)
2013-05-11 16:25:35.992 App [18843:c07] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet.'
* First throw call stack:
Taking the notification observer suggestion as a clue, I looked at my NSFetchedResultsController, presumably the only observer of the NSManagedObjectContext in scope:
-(NSFetchedResultsController *)typesController
{
if (_typesController) {
return _typesController;
}
// 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:#"IssueType" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Filter out the required types
NSPredicate *exclude = [NSPredicate predicateWithFormat:#"NONE name in %#",excludedTypes];
[fetchRequest setPredicate:exclude];
// 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:nil];
aFetchedResultsController.delegate = self;
self.typesController = aFetchedResultsController;
NSError *error = nil;
if (![_typesController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
[self failAlertWithMessage:nil forceAbort:YES];
}
return _typesController;
}
I found that, if I comment out the lines creating and assigning the predicate, it does not crash. Of course then I am showing some objects that I want hidden. I can't see anything wrong with the predicate itself, fetch does return the expected objects and excludes those in the excludedTypes Array as intended.
Save context method does not have any issues on editing or deleting existing objects, only on inserting a new object.
I am not using and threads here other than the main thread.
NONE is an alias for NOT ANY and is indeed for to-many relationships.
What you probably want is:
[NSPredicate predicateWithFormat:#"NOT name in %#", excludedTypes];
I have a managed object to represent a spot on a map. These points have zero-many types. I want to also show a sectioned table view. I had this working with an NSFetchedResultsController when type was a single value on the MapPOI object. But now that the types are in a different objects with a relationship between "MapPOI" called "types", how do I write the query (can I?)
Original:
- (NSFetchedResultsController *)newFetchedResultsControllerWithSearch:(NSString *)searchString
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"MapPOI"];
if(searchString.length)
{
request.predicate = [NSPredicate predicateWithFormat:#"name CONTAINS[cd] %#", searchString];
}
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"type" ascending:YES ],[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES ],nil ];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.campus.managedObjectContext
sectionNameKeyPath:#"type"
cacheName:nil];
aFetchedResultsController.delegate = self;
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error])
{
NSLog(#"Error performing institution fetch with search string %#: %#, %#", searchString, error, [error userInfo]);
}
return aFetchedResultsController;
}
i tried something like
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.campus.managedObjectContext
sectionNameKeyPath:#"types.name"
cacheName:nil];
But that caused
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid to many relationship in setPropertiesToFetch: (types.name)'
The NSFetchedResultsController is very flexible and can be used with all kinds of views, not only table views.
The sectionNameKeyPath is of course wrong. It clearly has to be a to-one relationship. Assuming that one MapPOI can only have one type, the key path should be something like #"type.name".
If, however one MapPOI can have several types, you could do the following:
Fetch the types entity rather than the POI entity. You do not need a section key path. Now objectAtIndexPath:indexPath.row will fetch a Type managed object.
For number of sections use
self.fetchedResultsController.fetchedObjects.count
For section titles use
[[self.fetchedResultsController.fetchedObjects objectAtIndex:section] name];
For row count in section use
Type *type = [self.fetchedResultsController.fetchedObjects objectAtIndex:section];
type.mapPOIs.count;
And it should be obvious how to populate the cell with the MapPOI entities.
I am working with a pretty complex object model and am having a bit of trouble with breaking some of my fetches down into sections to display in a tableview.
I have the need to group Meeting managed objects into a few different "pockets" such as project, client and a few others. For several reasons I've decided to implement these as tags that can be associated with a Meeting entity.
So I have created a new Tag entity which has a type and a value and established the relationships between the two:
Meeting <<-->> Tag
If I want to associate a Meeting with a project, I create a Tag with name 'project' and value 'Project Name', then add it to the Meeting entity via the relationship.
I initially thought about using a NSFetchedResultsController but I am getting all sorts of issues all of which I don't really understand.
For instance, this fetch (I'm omitting the unnecessary bits):
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[Meeting entityName] inManagedObjectContext:moc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"tags.name contains[] 'client'"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
[fetch setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:moc
sectionNameKeyPath:#"self.tags.value"
cacheName:nil];
In this particular case the fetch does work but somehow I am getting unexpected results where not only Tags with value client are presented but also ones with value project???
If I change my predicate to tags.name == 'project' I get an exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
I'm probably missing something basic here and admittedly I don't have a lot of experience with predicates but Apple's documentation on the subject leaves a lot to be desired.
As a side question and something that I don't understand either is why do I have to add self to my sectionNameKeyPath in self.tags.value? What does it do?? In this case if I don't add it I get an exception thrown as well:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid to many relationship in setPropertiesToFetch: (tags.value)
Finally, what is the alternative to using fetched results controller in this case? Will it be a bunch fetch requests where I first get each instance of Tag where name == 'project' and iterate through the array to pull the Meeting objects associated with it? It seems highly inefficient but all I can think of at the moment so if you have any other ideas I am very interested in hearing them.
Many thanks in advance for your time,
Rog
The issue is that Meeting has-many tags, so you're going to need to use aggregate operations:
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[Meeting entityName] inManagedObjectContext:moc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.name contains[cd] 'client'"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
[fetch setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch
managedObjectContext:moc
sectionNameKeyPath:#"clientName"
cacheName:nil];
i.e. Give me a list of all Meeting objects where ANY of the tags are of type client, and group them by clientName. For the clientName key path to work, you'll need to implement a transient property:
- (NSString *)clientName {
[self willAccessValueForKey:#"clientName"];
// Set clientName to the value of the first tag with name 'client'
NSString* clientName = #"...";
[self didAccessValueForKey:#"clientName"];
return clientName;
}
If a number of your NSManagedObject subclasses need the clientName property, you can implement it in a common abstract NSManagedObject subclass and make your concrete subclasses inherit from that.
"Apple's documentation on [NSPredicate] leaves a lot to be desired" - totally agreed!
tags.name is not valid because tags is a collection, there's no object, there are (let's say) three of them
i think you want something like "tags contains %#", projectTag but i'm not sure of the syntax. might be "%# IN %#", projectTag, thing.tags