First Letter Section Headers with Core Data - iphone-sdk-3.0

I'm trying to create a list of people sorted in a tableView of sections with the first letter as the title for each section - a la Address Book. I've got it all working, though there is a bit of an issue with the sort order. Here is the way I'm doing it now:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Contact" inManagedObjectContext:context]];
NSSortDescriptor *fullName = [[NSSortDescriptor alloc] initWithKey:#"fullName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:fullName, nil];
[request setSortDescriptors:sortDescriptors];
[fullName release];
[sortDescriptors release];
NSError *error = nil;
[resultController release];
resultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:#"firstLetter" cacheName:nil];
[resultController performFetch:&error];
[request release];
fullName is a standard property, and firstLetter is a transient property which returns - as you'd expect - the first letter of the fullName. 95% of the time, this works perfectly.
The problem is the result controller expects these two "lists" (the sorted fullName list and the sorted firstLetter list) to match exactly. If I have 2 contacts like John and Jack, my fullName list would sort these as Jack, John every time but my firstLetter list might sort them as John, Jack sometimes as it's only sorting by the first letter and leaving the rest to chance. When these lists don't match up, I get a blank tableView with 0 items in it.
I'm not really sure how I should go about fixing this issue, but it's very frustrating. Has anyone else run into this? What did you guys find out?

I am confused by what you mean by "first letter list".
You should not be creating such a list. You only implement the getter for the transient "firstLetter" property and use the fetched results controller's boiler-plate methods to implement the table view methods.
See this answer to How to use the first character as a section name for details.

I have not played with the section index too much but one thought would be to add the firstLetter sort as the first sort on your NSFetchRequest and add the fullName sort as a second sort.
If that doesn't work it would be great if you could post a sample project with the error so that we can play with it. If it turns out to be an Apple bug that same sample project can be used to submit the radar. If its not you are more likely to get someone to help solve it when they don't have to create the same project and try to rediscover the bug.
Plus there is always the off chance in the sample project that you stumble upon the answer. Happens to me all the time :)
UPDATE
I prefer Gerry3's answer.

Related

Complex NSPredicate traversing multiple Entities and relationship types

I'm having a tough time solving this predicate issue since the database structure is a bit complex. I have the following database structure, or at least what is of concern for this question:
PUBLISHER<< --- >>PUBLICIST<<--BOOK<<--->AUTHOR<<-->>AGENTS
I tried the following predicate that I have used when I traversed relationships in the past, but not to this degree. I should mention that I have an NSArray with agent names that I want to query against the database to determine a list of publishing houses the agent works with:
NSArray *agentNames = [NSArray arrayWithObjects:Dan, Hunter, Sloan, Jackson];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Publisher" inManagedObjectContext:context];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor
sortDescriptorWithKey:#"publisherName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)] ];
request.predicate = [NSPredicate predicateWithFormat:#"ANY pubHouse.publicist.assignedBook.authorRep.agentName IN %#", [agentNames valueForKey:#"agentName"]];
request.fetchBatchSize = 20;
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
When I run the previous predicate I get the following warning:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : ANY pubHouse.publicist.assignedBook.authorRep.agentName IN
I believe the predicate breaks when I travel the Book Entity relationship to the Author Entity and then onto the Agent Entity. At this point suggestions would help. Thanks
Your problem is that the predicate parser has no clue what set ANY should be applied to.
With this data model:
PUBLISHER<< --- >>PUBLICIST<<-->BOOK<<--->AUTHOR<<-->>AGENTS
… your keypath:
pubHouse.publicist.assignedBook.authorRep.agentName
… in terms of objects and set of the relationship looks something like:
object.set.object.object.set
So, that is two sets that the ANY could apply to.
You could try to build a subquery to handle the predicate but if you have to transverse that many relationships, your fetch will involve a big chunk of your data and will be very, very slow (assuming you get it work in the first place.)
Usually when you end up with a convoluted predicate like this it indicates that you are approaching the problem from the wrong end. In this case, it would be easier to start with a simple predicate like:
NSArray *agentNames = [NSArray arrayWithObjects:Dan, Hunter, Sloan, Jackson];
request.entity = [NSEntityDescription entityForName:#"Agent" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"agentName IN %#", agentNames];
Then you would walk the relationship keypath of:
authors.books.publicist.publishers
… to find all the related publishers.
I think that you will have trouble no matter what you do because having more than one to-many-to-many relationship e.g.
PUBLISHER<<--->>PUBLICIST
… increases the complexity of predicates and relationship walks exponentially. Usually, in such a case, you may need an additional entity to more thoroughly model one of the relationships. That usually reduces the complexity of the data model itself which simplifies fetches and walks.
Perhaps NSArray has a problem with valueForKey:. I have seen a solution that uses NSCompoundPredicate adding the array items in a loop.
BTW, in your chain of multiple relationships, aren't you missing your authors?
Just wrap the IN clause of your predicate string in parentheses:
#"ANY (pubHouse.publicist.assignedBook.authorRep.agentName IN %#)"
Would be glad to know if it works.

Which code is preferrable to automatically sort NSTableView

This is a CoreData/SQLite app. I have 2 ways to automatically sort the data in a certain way, on application start-up. Both work as expected, but I would like to ask you which method should be preferred, and why ?
This is the code I made after reading the doc from Apple:
[myTableView setSortDescriptors:[NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"entity_attribute" ascending:YES selector:#selector(compare:)], nil]];
This is somebody else's code which I found on the web:
NSSortDescriptor *myDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"entity_attribute" ascending:YES selector:#selector(compare:)] autorelease];
NSArray *sortedArray = [NSArray arrayWithObject:myDescriptor];
[myController setSortDescriptors:sortedArray];
Thanks for your advice.
The two samples you list are doing the exact same thing (and, in the exact same way).
Seems like a preference. Maybe the code you saw online, the author thought it was more readable to split it out into three lines, than to have it all on one big line.

Core Data NSFetchRequest for Specific Relationship?

I'm transitioning an existing data model that was previously stored in XML to Core Data, so I'm trying to learn the ropes as properly as possible. Core Data is obviously one of those technologies that isn't going anywhere anytime soon, so I might as well "learn it right."
Take for example a Core Data model with two Entities:
Person
Food
Person has 2 one-to-many relationships with Food:
favoriteFoods (1-to-many)
hatedFoods (1-to-many)
(Both Person and Food are subclasses of NSManagedObject as well.)
In the previous data model, Person maintained two NSArray instance variables. If I wanted favorite foods, I could call:
Person *fred = [[Person alloc] init];
NSArray *fredsFavorites = fred.favoriteFoods;
Easy squeezy.
I'm reading through the documentation for Core Data, and I can't seem to find the right way to obtain this NSArray given an NSFetchRequest, because I can't define which relationship I want to obtain objects from.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Food" inManagedObjectContext:[fred managedObjectContext]]];
[request setIncludesSubentities:NO];
NSArray *fredsFavoriteAndHatedFoods = [[fred managedObjectContext] executeFetchRequest:request error:nil];
This returns all of the Food items stored in both favoriteFoods and hatedFoods. How can I split these up? Surely there's a simple explanation, but I don't currently grasp the concept well enough to explain it in Core Data jargon, thus my Google searches are fruitless.
The most straightforward way to get it is to simply access the relationship NSSet directly:
NSArray *fredsFavorites = [fred.favoriteFoods allObjects];
(I've shown how to get an NSArray from the resulting NSSet).
Alternatively, if you haven inverse relationship set up (which you should) you could use a fetch request like this:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Food" inManagedObjectContext:[fred managedObjectContext]]];
[request setPredicate:[NSPredicate predicateWithFormat:#"ANY personsWithThisAsFavorite == %#", fred]];
NSArray *fredsFavoriteFoods = [[fred managedObjectContext] executeFetchRequest:request error:nil];
This assumes that personsWithThisAsFavorite is the inverse relationship to favoriteFoods. Unless I'm reading your example wrong, favoriteFoods should really be a many-to-many relationship, since a person can have multiple favorite foods, and a food can have multiple people with that food as a favorite.
(Note that I haven't tested this, so the NSPredicate might not be 100% correct)

Entity is not key value coding-compliant for the key

if (win) {
// Game was won, set completed in puzzle and time
// Calculate seconds taken
int timeTaken = (int)([NSDate timeIntervalSinceReferenceDate] - self.gameStartTime);
int bestTime = [[self.puzzle valueForKey:#"bestTime"] intValue];
if (timeTaken < bestTime && bestTime != 0) {
[self.puzzle setValue:[NSNumber numberWithInt:timeTaken] forKey:#"bestTime"];
NSLog(#"Best time for %# is %#", [self.puzzle valueForKey:#"name"], [self.puzzle valueForKey:#"bestTime"]);
}
}
This is some code from an iPad game I am making and I am using Core Data for storing the levels. When a level is completed and won, I want to set the best time for that level. The time taken is calculated, and if it is better than the previous best time, I want to set it as the best time for the level.
This code fails on the 'int bestTime' line when it tries to retrieve the best time from self.puzzle which is an NSManagedObject from Core Data. The best time is stored as an Integer 32 in the Core Data model. It fails with a SIGABRT error.
'[<NSManagedObject 0x95334d0> valueForUndefinedKey:]: the entity Puzzle is not key value coding-compliant for the key "bestTime".'
I have searched online for reasons as to why this is happening and how to fix it, but nothing seems to have helped. There are other places where I access Integer values from the Core Data model and they work perfectly, although they are used to filter and sort queries.
I also don't know if the line where I set the value will work.
Any help on this would be greatly appreciated.
EDIT: This is the code that fetches an array of puzzles of which one is taken to be the above puzzle.
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Puzzle" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Set the filter for just the difficulty we want
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"difficulty == %d", difficulty];
[request setPredicate:predicate];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortid" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
Ok, Firstly, I would like to thank everyone who suggested ideas. They may not have helped me solve the problem, but I learnt more about Core Data and it is always good to find out what I should be checking when things don't work.
I don't really know what the problem was. Until this morning I had Xcode open for about 5 days I think and yesterday I added the attribute 'bestTime' to the data model. I can only assume that over the 5 days, Xcode had become a little unstable and thought it was saved when it wasn't. I had checked that I had saved the model attributes, in fact I must have checked 3 or 4 times as well as my habit of hitting Command+S after any change I make.
Anyway, I rebooted my machine earlier today and when I started up Xcode a few minutes ago I realised that 'bestTime' was not in the model file. I added it, reset the settings on the iPad simulator and it worked.
Thank you all again for the help, sorry the solution wasn't more interesting and code based. Although it makes me feel better that my code wasn't the cause.
That managed object doesn't have an attribute named “bestTime”. According to the exception message, it definitely is a Puzzle, so you haven't declared an attribute named bestTime in your model (or you misspelled it or capitalized it differently).
I did solve the same problem by delete and create the data model again and clean then rebuild again.
I think the bug is caused by core data does not update some data inside sometimes.
I don't think there's enough information here to determine the cause. You might try reading the Core Data Troubleshooting Guide; one possible cause could be if you initialized this particular instance of Puzzle using plain init rather than initWithEntity.
If you added attribute bestTime to the model at the later time, you might have forgotten to put declaration and implementation for them in the connected Managed Object Class.
Try convenience actions provided in Design -> Data Model -> Copy Objective-C ... Method Declarations/Implementations to Clipboard (when editing your Model file).
If parsing JSON into a managed object, be sure you're using the coreDataPropertyName property rather than the json-key-name key from JSON. Easy to mix up when they're named so similarly.
This error was driving me nuts, and all because I was using image-url rather than imageURL.

iPhone's Core Data crashes on fetch request

I'm using the following code to grab a few objects from SQLite store (which is a prepared SQLite db file, generated with Core Data on desktop):
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity: wordEntityDescription];
[request setPredicate: [NSPredicate predicateWithFormat: #"word = %#", searchText]];
NSError * error = [[NSError alloc] init];
NSArray * results = [[dao managedObjectContext] executeFetchRequest: request error: &error];
Eveyrthing seems to be setup properly, but executeFetchRequest:error: fails deeply inside Core Data (on NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument) producing 256 error to the outside code.
The only kink I had setting up managedObjectContext is I had to specify NSIgnorePersistentStoreVersioningOption option to addPersistentStoreWithType as it was constantly producing 134100 error (and yes, I'm sure my models are just identical: I re-used the model from the project that produced the SQL file).
Any ideas?
P.S. Don't mind code style, it's just a scratch pad. And, of course, feel free to request any additional info. It would be really great if someone could help.
Update 1
Alex Reynolds, thanks for willingness to help :)
The code (hope that's what you wanted to see):
NSEntityDescription * wordEntityDescription; //that's the declaration (Captain Obviousity :)
wordEntityDescription = [NSEntityDescription entityForName: #"Word" inManagedObjectContext: ctx];
As for predicate – never mind. I was removing the predicate at all (to just grab all records) and this didn't make any differences.
Again, the same code works just fine in the desktop application, and that drives me crazy (of course, I would need to add some memory management stuff, but it at least should produce nearly the same behavior, shouldn't it?)
Can you add code to show how wordEntityDescription is defined?
Also, I think you want:
NSError *error = nil;
You may want to switch the equals symbol to like and use tick marks around the searchText field:
[request setPredicate: [NSPredicate predicateWithFormat: #"word like '%#'", searchText]];
NSPredicate objects are not put together like SQL, unfortunately. Check out Apple's NSPredicate programming guide for more info.