I have a core data entity called "TruckNumber" which has a string as it's only property. The string is usually a 1-3 digit integer (as a string) but sometimes can have letters such as TMP9. The name of the property is "itsNotANumma". I am doing a fetch request to populate a picker, but they are not being sorted and I don't know why. I've used this exact technique on other entities for other pickers and never had a problem. Please help... Here's the relevant code:
// Fetch truck numbers
NSFetchRequest *truckNumberFetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *truckEntity = [NSEntityDescription entityForName:#"TruckNumber" inManagedObjectContext:self.managedObjectContext];
[truckNumberFetchRequest setEntity:truckEntity];
// Sort Descriptor
NSSortDescriptor *truckDescriptor = [[NSSortDescriptor alloc] initWithKey:#"itsNotANumma" ascending:YES];
NSArray *truckSortDescriptors = [[NSArray alloc] initWithObjects:truckDescriptor, nil];
[inventoryFetchRequest setSortDescriptors:truckSortDescriptors];
error = nil;
NSArray *truckResults = [managedObjectContext executeFetchRequest:truckNumberFetchRequest error:&error];
if (error)
NSLog(#"Unresolved error while saving context: %#, %#", error, [error userInfo]);
truckNumbersArray = [[NSMutableArray alloc] init];
for (TruckNumber *truckNumber in truckResults)
{
[truckNumbersArray addObject:truckNumber.itsNotANumma];
}
Here is my data model for truck number:
These are the results:
Looks like you have a simple typo:
[inventoryFetchRequest setSortDescriptors:truckSortDescriptors];
Should be:
[truckNumberFetchRequest setSortDescriptors:truckSortDescriptors];
I would assume anyway, from reading that code.
[NSSortDescriptor sortDescriptorWithKey:#"self" ascending:YES comparator:^(id obj1, id obj2){
return [(NSString*)obj1 compare:(NSString*)obj2 options:NSNumericSearch];
}];
It works so perfectly if you have numbers in a range from 1-5000 or onwards.
Related
I would like to sort an array of Reviews fetched from Core Data by the number of times a given book is reviewed. The Entity is reviews and the review entity has an attribute bookid. So for the following table:
reviewid|bookid|review
1|1|This is a great novel...
2|1|Wonderful novel
3|2|Ok biography.
4|3|Horrible romance
I would like to return an array that has bookid=1 at the top as it has two reviews whereas the others have one.
How can I do this using NSSortDescriptor?
I believe, you can do it to the array after the fetch using the following but it seems that it should be possible to do it directly in the core data results
NSEntityDescription * entity = [NSEntityDescription entityForName:#"Parent" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:reviews];
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"bookid.#count" ascending:NO];
NSArray *descriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSArray *sortedArray = [results sortedArrayUsingDescriptors:descriptors];
You can set the NSFetchRequest's sort descriptor.
request.sortDescriptors = #[sortDescriptor]
Read the documentation for more info.
https://developer.apple.com/documentation/coredata/nsfetchrequest/1506262-sortdescriptors
I am trying to adapt a method I was using to get a dictionary of ids from a core data request to an array of objects. However, I am getting confused about what array is the one I want. Basically, I want an array of contacts with all the appropriate attributes. Which array of contact objects in the following is the one I want, the fetchedObject or the tmpArray?
-(NSMutableArray *) getContactsNeedingSync
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [IDModel sharedInstance].managedObjectContext;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"needsync==#1"];
fetchRequest.predicate = predicate;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contacts" inManagedObjectContext:context];
fetchRequest.entity = entity;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"cid" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
NSError *error = nil;
if (![fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSArray *fetchedObjects = fetchedResultsController.fetchedObjects;
//is this the array I want?
NSInteger resultCount = [fetchedObjects count];
if (resultCount == 0) {
NSLog(#"result Count is zero");
return [NSMutableArray array];//nothing in the Core Data store
}
else {
Contacts *contact;
NSMutableArray *tmpArray = [NSMutableArray array];
int i;
for (i = 0; i < resultCount; i++) {
contact = fetchedObjects[i];
tmpArray[contact.cid] = contact;
}
return tmpArray;//is this array I want?
}
context = nil;
}
Your code creates a fetch request and sets it up to collect a sorted set of objects. You then use an NSFetchedResultsController to collect the results for the fetch request. You then have a bit of code which resorts the sorted data (init tmpArray).
You can simplify this by removing the NSFetchedResultsController and just executing the fetch request directly. The NSFetchedResultsController isn't helping you as you aren't adding a delegate to it or retaining it.
The data is also already sorted because of the NSSortDescriptor. Indeed, if it was't you'd get crashes as you'd be trying to insert into an array that wasn't big enough... So, remove tmpArray and the associated code.
I am using an NSFetchedResultsController and need to return distinct objects based on their latest dates and name. Each date is stored as an NSDate.
Example:
Object1
name:Object1
date:01/01/2001
Object2
name:Object
date:01/02/2001
Object3
name:OtherObject
date:01/10/2001
Object4
name:OtherObject
date:02/01/2001
Expected results (Return only the latest date objects when a duplicate is found):
name:Object
date:01/02/2001
name:OtherObject
date:02/01/2001
// Below returns everything sorted correctly although I only need the latest of each match based on the date.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *name = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *date = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:name, date, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
EDIT
As of now I am fetching all of the objects into an array, for each object in array1 I cycle thru and add / compare it to the latest matching objects in array2 with the same name and remove the oldest date entry from array2 then I'm left with unique latest dates in array2. I was hoping there was a less cumbersome way to do this with a single fetchrequest / subquery.
Core Data doesn't automatically track insertion order. When you get an fetched object back, its entities will be in any random order.
My immediate thoughts on this are that you need another attribute to track the insertion time and reorder/sort by this attribute.
So, your attribute would be need to be a date time with seconds. You would call this:
NSDate *now = [[NSDate alloc] init];
and store it in the attribute. When you fetch the object, sort by the key of this attribute.
Try this...
Create an NSSet of the attribute name to return a unique set of data.
Fetch the latest attribute date for each attribute name.
For example:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *objects = [self.managedObjectContext executeFetchRequest:fetchRequest];
NSMutableArray *arrayObjectNames = [[NSMutableArray alloc] init];
for (Object *object in objects) {
[arrayObjectNames addObject:object.name];
}
NSSet *setObjectNames = [NSSet setWithArray:arrayObjectNames];
NSString *key = #"name";
NSMutableDictionary *dictNamesDates = [NSMutableDictionary dictionary];
for (NSString *objectName in setObjectNames) {
NSFetchRequest *fetchName = [[NSFetchRequest alloc] initWithEntityName:#"Object"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#", key, objectName];
[fetchName setPredicate:predicate];
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
[fetchName setSortDescriptors:#[sortDate]];
Object *targetObject = [[self.managedObjectContext executeFetchRequest:fetchName] lastObject];
if (targetObject) [dictNamesDates setObject:targetObject.date forKey:objectName];
//OR as an alternative but achieving the same outcome...
if (targetObject) [dictNamesDates setValue:targetObject.date forKey:targetObject.name];
}
This should create an NSMutableDictionary that contains each attribute name as the key and the corresponding latest attribute date as the value.
Does this help?
In my app i've some records in core data and i want to fetch last 20 records in ascending order i.e.. if there is 30 records than i want to fetch records from 11 to 30 in ascending order -
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Messages" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:20];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"messageId" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"((userId == %#) && (frndId == %#) )",uid, fid];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray* dataArray = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
for (Messages* msg in dataArray) {
//code here
}
but it gives me first 20 records in ascending order and if i change to
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"messageId" ascending:NO];
then i got last 20 records but that was in ascending order?Any suggestion would be appreciated.
cant you just set this offset?
//assume it fits an nsinteger
NSInteger count = [managedObjectContext countForFetchRequest:fetchRequest /*the one you have above but without limit */ error:&error];
NSUInteger size = 20;
count -= size;
[fetchRequest setFetchOffset:count>0?count:0];
[fetchRequest setFetchLimit:size];
NSArray* dataArray = [managedObjectContext executeFetchRequest:fetchRequest error:&error]
you can use -
//your code as it is till this line but with
//NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"messageId" ascending:NO];
NSArray* dataArray = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
NSArray *sortedArray;
sortedArray = [dataArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [(Messages*)a messageId];
NSString *second = [(Messages*)b messageId];
return [first compare:second];
}];
for (Messages* msg in sortedArray) {
//your code
}
Happy coding :P
NSFetchRequest does not provide a method for that. You have to fetch 20 records in descending order and then reverse the resulting data array:
dataArray = [[dataArray reverseObjectEnumerator] allObjects];
If this is for display in a table view, then instead of actually reversing the data array
you could adapt the cellForRowAtIndexPath method to display the object
dataArray[dataArray.count - 1 - indexPath.row]
instead of
dataArray[indexPath.row]
For some reason all of the NSString typed attributes are being returned as NSArrays in my Article object. Here's my function to retrieve them:- (NSArray *)getSavedArticles
{
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSError *error = nil;
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Article" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *dateSort = [NSSortDescriptor sortDescriptorWithKey:#"last_opened" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:dateSort]];
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
return fetchedObjects;
}
I have not created a NSManagedObjectModel for these, and instead I'm just accessing them using KVO.
//self.data has the array returned from getSavedArticles
NSManagedObject *object = [self.data objectAtIndex:indexPath.row];
NSString *path = [object valueForKey:#"path"];
NSString* id = [object valueForKey:#"id"];
When I look at this in the variables pane, on path and id I see (__NSArrayI *) ... Variable is not a CFString. Printing the description of either of these also prints out the parenthesis used when printing arrays.
I'm just wondering if this could be due to the sort descriptor? I have double checked that the data going into these objects is typed correctly, and the SQLite database that I'm using to backup everything is displaying strings.
I have tried re-installing the app in the simulator, and resetting it. I still get a NSArray instead of a NSString.
I really don't know what's going on here. Any help would be appreciated.
EDIT: I just found something else interesting. I do another check to see if an Article has been saved, and this time I don't get a NSArray for the article path:- (NSString *)hasArticleSaved:(NSString *)id
{
NSString *path = nil;
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"id", id]];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Article" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects.count > 0) {
NSManagedObject *savedArticle = [fetchedObjects objectAtIndex:0];
path = [savedArticle valueForKey:#"path"];
}
return path;
}
Now I'm really confused. Any takers?
Your problem is here:
[self.data objectForKey:indexPath.row]
...because objectForKey: is looking for a string key name e.g. "path" or "id". Giving it an integer will produce an error or a random return.
Instead, you want:
[self.data objectAtIndex:indexPath.row]
... which will return the managed object at the given index in the array.
I found the issue. I had changed the data structure for my table so self.data is an array of arrays...and with only 1 object it looked like I was getting a NSArray back when in fact I was just accessing the data wrong. I just needed to do [[self.data objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];