issue with sorting data in UITableview - iphone

I have implement a UItable with data sourced via Core Data. The table works fine and presents the data correctly, drills down.. etc.
However, it has the following problem: it presents the content data in a different order every time. I would like it at least to appear consistently or even better alphabetically.
Any ideas on why this might be happening or a specific property or method I should be reviewing in the docuemntation?
Help much appreciated

You need to set an NSSortDescriptor on your NSFetchRequest
NSSortDescriptor *sortDescriptorName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptorName]];
Then create your NSFetchedResultsController with the fetchRequest object.
This would cause your list to appear sorted alphabetically using the property "name" and ignore case while sorting.

You can simply add a sort descriptor to the request. In this example, the data object has a numeric column for "sortOrder", but you could sort on most anything.
- (NSMutableArray *)loadData {
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameOfEntity" inManagedObjectContext:context];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortOrder" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
[request release];
return [mutableFetchResults autorelease];
}

I have not done much with CoreData other than to read about it and review some examples, but I would say this:
It sounds like your data is simply coming out of CoreData unsorted, and the order is in fact not guaranteed UNLESS you provide some additional hint about how you would like it sorted, with say a sort descriptor or something.
Sorry I can't provide a concrete reference, but there has to be a way to both fetch data and specify the order in which it is returned.
Alternatively, you could (behind the scenes) fetch all the table data from CoreData and then sort it yourself, but I think that defeats the purpose of using CoreData in the first place and discards a lot of the functionality of CoreData that is likely more efficient than anything you could write yourself to massage the data.

Related

Overwriting Core Data information using NSPredicate

I'm trying to edit an entry in a Core Data entry using NSPredicate, but I'm not entirely sure as to how it works.
I'm trying to get an entity based on an id from another object, but I can't see where i'm going wrong. Here's my data model in effect:
Entity: myEntity, Attributes: name, id, value.
I'm trying to retrieve the correct object in the Database via this:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"myEntity" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %#", self.itemToEdit.ID];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error;
MyEntity *myEntity = [[self.managedObjectContext executeFetchRequest:request error:&error] objectAtIndex: 0];
myEntity.value = self.itemToEdit.anotherValue;
[self.managedObjectContext save:&error];
For whatever reason, the data is not being saved, the app isn't crashing either which leads me to believe the issue is with the predicate. Anyway, what's wrong with the code? As a note, itemToEdit is not of type myEntity, it is another object, but the assignment types are the same.
Regards,
Mike
Case closed guys. Stupid mistake by me. I was so focussed on making this new part of Core Data work (which I find daunting as it is), that I actually forgot to assign self.itemToEdit.value. Sorry for wasting your time.
Mike

CoreData Math Functions

I've got a column with integer values in CoreData. While retrieving results from it, I want the column values to be subtracted with a number.
Something like: columnValue - someNumber (this number is entered by user)
I know I may have to use NSPredicate for this, but am unaware if there's a function or syntax for it.
The alternate right now I have is to iterate all column values and subtract with 'someNumber'. But I think there should be a better and efficient way to do this.
Edit: Code from #salo.dm 's answer
- (NSDictionary *)myFetchResults {
//Predicate works fine
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:pred1, pred2, nil]];
/*Sort Descroptor - Sorting by 4 columns*/
NSSortDescriptor *sortDesc1 = [NSSortDescriptor sortDescriptorWithKey:#"Column1" ascending:YES];
NSSortDescriptor *sortDesc2 = [NSSortDescriptor sortDescriptorWithKey:#"Column2" ascending:YES];
NSSortDescriptor *sortDesc3 = [NSSortDescriptor sortDescriptorWithKey:#"Column3" ascending:YES];
NSSortDescriptor *sortDesc4 = [NSSortDescriptor sortDescriptorWithKey:#"Column4" ascending:YES];
/*Get Data*/
MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableName" inManagedObjectContext:context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDesc1, sortDesc2, sortDesc3, sortDesc4, nil]];
NSArray *listData = [context executeFetchRequest:fetchRequest error:nil];
/*Create subtract expression*/
NSExpressionDescription *subExp1 = [[NSExpressionDescription alloc] init];
[subExpLatitude setName:#"subtraction1"];
[subExpLatitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column3"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber1]],
nil]]];
[subExp1 setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *subExp2 = [[NSExpressionDescription alloc] init];
[subExpLongitude setName:#"subtraction2"];
[subExpLongitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column4"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber2]],
nil]]];
[subExp2 setExpressionResultType:NSDoubleAttributeType];
/*Get difference data*/
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, subExp2, nil]];
NSArray *listDifference = [context executeFetchRequest:fetchRequest error:nil];
NSLog(#"Subtraction 1: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction1"]);
NSLog(#"Subtraction 2: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction2"]);
NSMutableDictionary *dictResult;
[dictResult setObject:listData forKey:#"Data"]
[dictResult setObject:listDifference forKey:#"Difference"]
return dictResult;
}
Edit: Get coredata object
This doesn't work.
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]]; //Searches for a column for the name specified
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Had to change it to below to get it working (Assuming this is the correct way)
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForEvaluatedObject]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];
I added expEntity to the setPropertiesToFetch list. Now I get two values in the dictionary.
{
TableNameEntity = "0x5e22120 <x-coredata://1A659A52-9321-4ACD-992B-04F20E7BDCED/TableNameEntity/p1640>";
subtractionValue = "-24.13";
}
When I try to retrieve and access TableNameEntity from the dictionary, the app crashes.
TableNameEntity *tableEntity = (TableNameEntity *)[dict objectForKey:#"TableNameEntity"];
tableEntity.column1 //This is not the exact code. But this operation crashes with error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_NSObjectID_48_0 column1]: unrecognized selector sent to instance 0x5e22120'
Here if you notice, the value for key TableNameEntity is contained in quotes, so I guess its being returned as a string.
See if you can correct what I've done wrong.
I've tried an alternate to get columns values in the dictionary. Here it is (this works fine). But I guess its not a good approach.
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, #"column1", #"column2", ... #"columnN" nil]];
You could make the calculation in a fetch request as follows:
- (NSArray *)myFetchResults
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"myEntity" inManagedObjectContext:myContext];
request.resultType = NSDictionaryResultType;
NSExpressionDescription *subExDescr = [[NSExpressionDescription alloc] init];
[subExDescr setName:#"subtraction"];
[subExDescr setExpression:[NSExpression expressionForFunction:#"subtract:from:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForConstantValue:[NSNumber numberWithInt:someNumber]],
[NSExpression expressionForKeyPath:#"myAttribute"],
nil]]];
[subExDescr setExpressionResultType:NSInteger64AttributeType];
request.propertiesToFetch = [NSArray arrayWithObject:subExDescr, nil];
NSError *err = nil;
NSArray *results = [self.moContext executeFetchRequest:request error:&err];
[request release];
[err release];
return results;
}
The fetch results will be an array of dictionaries. You can access the result for the nth value in the column as follows:
NSArray *results = [self myFetchResults];
NSDictionary *nthDict = [results objectAtIndex:n];
NSInteger nthValue = [nthDict objectForKey:#"subtraction"];
Note that this code is untested. As is, I believe it will operate on all items in the column. If you want to operate only on selected items, you can add a predicate to select the items you want to operate on.
You could also look up the documentation for NSExpression and build all sorts of different operations. The class is a bit dense, but the Apple documentation has some snippets of code that help to understand how to use it. And I hope the above example illustrates how to incorporate it into a fetch request.
EDIT: CORRECTION
The entity, of course, has to be specified in the fetch request. I had initially left that out, but have now corrected the code.
EDIT: RESPONSE TO COMMENT
I'm not sure I understand what you're asking, but this may be it. You can create expression descriptions as follows:
NSExpressionDescription *expLatitude = [[NSExpressionDescription alloc] init];
[expLatitude setName:#"latitude"];
[expLatitude setExpression:[NSExpression expressionForKeyPath:#"Column3"]];
[expLatitude setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Then, you add them to the propertiesToFetch array, as two more objects. Each dictionary in the fetch results will now have the latitude, the subtraction resulting from that same latitude, and the corresponding objectID of the entity that contained that latitude. The dictionaries will be ordered in the results array according to your sort descriptors. (I haven't tried the objectID expression, but I think it should work fine.)
Basically, your results are ordered in the exact same order as a traditional fetch request with the same predicate and the same sort descriptors, that is, for a fetch request with the default result type NSManagedObjectResultType.
I hope this answers your question. If not, don't hesitate to ask again. But, I may take a while to answer because it's sleep time for me now.
EDIT: RESPONSE TO 'GET COREDATA OBJECT'
Good catch on finding the correct expression to get the object ID! (Seeing it, the expression I offered for this now looks obviously wrong.)
As to the exception you're getting, it makes sense. The value returned in the fetch results is not the managed object itself, it's only the managed object's ID. To access the managed object, I think the following should work:
NSManagedObjectID *myObjectID = [dict objectForKey:#"TableNameEntity"];
TableNameEntity *tableEntity = (TableNameEntity *)[context objectWithID:myObjectID];
tableEntity.column1
The context above is the NSManagedObjectContext.
However, I think I prefer your final solution. I didn't know you could combine NSExpressionDescriptions with properties in the propertiesToFetch array. Good to know!
More importantly, it may be faster to get all the properties you need in the fetch than to get only the objectID from the fetch and get the properties later. Getting the objectID generally does not fire the fault for the entity. I believe the fault will be fired later, when you access the properties. It will fire once, when accessing the first property, or multiple times, once for each property. (I'm not sure which.) [For an explanation of faulting, see Firing Faults.]
My recommendation is that including all the properties you need in propertiesToFetch is the best approach. (You may try getting the objectID, if you prefer. But, if you find it's slow, you can go back to getting all the properties in the fetch.)
Fetch requests and expressions are poorly documented. You have to play with them a bit to get the syntax right. You seem to be doing very well.

Sort on to-many relationship using a NSFetchedResultsController

I'm trying to use the NSFetchedResultsController in my app, but have a problem to sort my data. I get the following error when trying to sort the result using a relationship that is two levels down from the entity:
* Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'to-many key not allowed here'
My data model is set up this way:
Item <<---> Category <--->> SortOrder
<<---> Store
In other words: Each item belongs to one category. Categories can have different sort orders for each store that includes a certain category.
So, I'm creating a fetch request to find all items for a certain store and would like to present the result using category names as sections, and sorted on the sort order.
When I perform the the fetch (last line below), I get the above error.
NSManagedObjectContext* context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(status != %d) AND (ANY category.sort.include == YES) AND (ANY category.sort.store == %#)", ItemStatusDefault, store];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"category.sort.order" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
self.resultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"category.name"
cacheName:nil];
[fetchRequest release];
NSError *error;
BOOL success = [self.resultsController performFetch:&error];
If I change the sorting to, say, category names, it works.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"category.name" ascending:YES];
How can I get the NSSortDescriptor to sort on the sort order?
UPDATE:
So it seems this is not possible. I got a suggestion to create a transient property and sort on that, but Apple documentation clearly states
You cannot fetch using a predicate
based on transient properties
My conclusion here is that I cannot use NSFetchedResultsController out of the box. I need to either access the array of objects the NSFetchResultsController gives me and sort in memory, or setup my own fetch requests and skip NSFetchedResultsController.
iOS 5 provide now ordered relationships
https://developer.apple.com/LIBRARY/ios/releasenotes/DataManagement/RN-CoreData/index.html
UPDATE:
Link updated
Reference : "Core Data Release Notes for OS X v10.7 and iOS 5.0"

How do you sort NSNumber with NSSortDescriptor?

I'm having trouble sorting an NSNumber using NSSortDescriptor. For whatever reason it won't sort into the correct order. What am I doing wrong?
- (NSNumber *)sortNumber {
NSNumber *sum = [NSNumber numberWithFloat:([self.capacity floatValue] / [self.availability floatValue])];
return sum;
}
It is defined as an Int32 in the xcdataModel and called by this class to sort.
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setReturnsObjectsAsFaults:NO];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext]];
NSArray *sortDescriptors = nil;
} if ([fetchSectioningControl selectedSegmentIndex] == 0) {
sortDescriptors = [NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"sortNumber" ascending:YES selector:#selector(compare:)] autorelease]];
}
[fetchRequest setSortDescriptors:sortDescriptors];
EDIT:Yeah, in the cold light of day this needs a little more explanation. I'll try to explain the project. This is a CoreData app, the values 'capacity' and 'availability' are derived from parsing an XML file with SAX and attaching them to the Managed Object Model where they are defined as Strings, originally they would have been numeric in the XML.
These are then defined in a Class where the calculation above has been made and attached to the same Object Model (if the code above is right then perhaps this is where the problem is?). All this has been in effort to obtain a percentage value that I would like to use to sort a TableView. (BTW, I realise they need swapping around, oops) The second bit of code is where this happens using NSFetchResultsController and ManagedObjectContext. I know this bit works because I'm also sorting this list by other attributes set to if selectedSegmentIndex == 0 etc. They all sort correctly.
I hope this makes a bit more sense.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortNumber" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
you can use NSSortDescriptor without calling compare method. It will sort itself in you desired order.

iPhone Core Data saving multiple items at once random behavior?

I have an application that reads an rss feed, parses the xml and adds it to my database using Core Data (this is so the user can see the feed even if no internet connection is available) this all works fine. The way I am doing the parsing is: on the didStartElement i create a new Entity such as:
NewsDB *newsDB = [NSEntityDescription insertNewObjectForEntityForName:#"NewsDB" inManagedObjectContext:managedObjectContext];
self.currentObject = newsDB;
and in the didendDocument i just save everything with something such as:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSError *error = nil;
if (![managedObjectContext save:&error])
{
NSLog(#"Error saving %#", error);
}
This al works perfectly fine, in fact my program works just the way I want it now. But my question is when the managed object context gets saved the items seem to be added randomly, this is the first created object in the context may not be the first row in the database. I fixed this by adding a column that tells me the position in the xml, and then simply sorting by this column in my fetchedResultsController.
I know I could just save the context every time an item ends, but that doesn't sound like a good approach, so I just save them all at the end.
My question is why do they get added randomly?, is this the normal behavior?. Thank you.
-Oscar
The currency of Core Data are NSSet* instances. Sets are unordered, so anything you add and then fetch back will come back to you without any intrinsic ordering.
So you just need to apply an NSSortDescriptor when you initialize your NSFetchedResultsController. You can apply as many sort orderings as you like, e.g.:
NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES selector:nil];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:lastNameDescriptor, firstNameDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[lastNameDescriptor release];
[firstNameDescriptor release];
...
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"keyInitial" cacheName:#"MyObjects"];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
[aFetchedResultsController release];
[fetchRequest release];
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
// handle error...
}
This is normal behavior. You may have also noticed that when you create a to-many relationship that the related property is an unordered collection (NSSet).
When order matters, simply add a number attribute for sorting as you have done.