CoreData doesn't fetch any rows - iphone

I have a very basic CoreData backed iPhone application. After I forced the app to generate the sqlite file, I took it and prepopulated it with one record to test loading it into tableview.
I've hit a snag, though, because CoreData doesn't seem to be finding the row in the table.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
The first function always returns one and the second function always returns zero. Since the tableview thinks there are no rows, it never hits cellForRowAtIndexPath, so none of my data is loaded.
I can, however, see my table structure in viewDidLoad with the following code:
if(!managedObjectContext){
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Assessment" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
for (NSPropertyDescription *property in entity)
{
NSLog(#"%#", property.name);
}
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[request release];
Now, it strikes me that nothing in the rest of my code ever generates an NSFetchRequest because I never hit cellForRowAtIndex. But I also based most of this code on the Recipe example, and it looks like it loads in the same way (and it actually works).
I'm sure I'm missing something obvious here, can someone point me in the right direction?
The code can be found in it's entirety here.

The problem is almost certainly in the setup of the fetched results controller e.g. wrong entity, wrong predicate etc.

Related

NSManagedObject becomes null

Im using a NSManagedObject as an attribute within my ViewController, declared like this:
#property(retain, nonatomic)NSManagedObject *person;
Im propagating the content of a fetch to a UITableView, when the user taps the contact he is looking for, this is what happens:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
self.person = [contacts objectAtIndex:indexPath.row];
Contacts contains the result of the fetch, which is done like this:
NSArray *contactsArray;
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [(OAuthStarterKitAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Contacts" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
testForTrue = [NSPredicate predicateWithFormat:#"SOME CONDITION"];
[fetchRequest setPredicate:testForTrue];
[fetchRequest setFetchLimit:10];
contactsArray = [[NSArray alloc]initWithArray:[context executeFetchRequest:fetchRequest error:&error]];
When the user taps the contact, self.person has that value, but when I try to use that value in another method it's nil, and the address is 0x000000.
This only happens on iOS 5, on iOS 6 person has the value of the contact selected and I can use elsewhere.
Any ideas?
Thanks.
Instead of getting the NSManagedObject, which is always dependant of the life cycle of the context I just stored de NSManagedObjectID and then get the NSManagedObject by using the function objectWithID. And it worked!

Dynamically setting fetchLimit for NSFetchedResultsController

I'm using am NSFetchedResultsController to populate data onto a UITableView.
It's a simple chat app and I want to load the latest 25 messages onto the table first and load more as the user scrolls up to see older messages (the chat message are in a ascending order).
I call a method that will setFetchLimit: for the NSFetchedResultsController in the willDisplayCell: like so....
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0)
{
[self performSelector:#selector(getMoreMessages) withObject:nil afterDelay:1.0];
}
}
when the first row of the UITableView has been displayed, getMoreMessages will try to reset the fetchLimit reload the UITableView like so.....
- (void)getMoreMessages
{
maxListItems += 25;
NSLog(#"set maxListItems: %d", maxListItems);
[self.resultsController.fetchRequest setFetchLimit:maxListItems];
[self._tableView reloadData];
}
However, it doesn't seem to be working, the table data will not change.
The initial NSFetchRequest is set like so...
NSFetchRequest *chatDataRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ChatData" inManagedObjectContext:appDelegate.managedObjectContext];
[chatDataRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(key != 0 OR messageNo != 0) and matchNo = %d", matchNo];
[chatDataRequest setPredicate:predicate];
NSSortDescriptor *sortDescripter1 = [[NSSortDescriptor alloc] initWithKey:#"status" ascending:YES];
NSSortDescriptor *sortDescripter2 = [[NSSortDescriptor alloc] initWithKey:#"messageNo" ascending:YES];
NSArray *sortDescripters = [[NSArray alloc] initWithObjects:sortDescripter1, sortDescripter2, nil];
[chatDataRequest setSortDescriptors:sortDescripters];
[sortDescripters release];
[sortDescripter1 release];
[sortDescripter2 release];
[chatDataRequest setFetchLimit:25];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:chatDataRequest managedObjectContext:appDelegate.managedObjectContext sectionNameKeyPath:nil cacheName:[NSString stringWithFormat:#"%d_chat.cache", matchNumber]];
[chatDataRequest release];
fetchedResultsController.delegate = self;
NSError *error;
BOOL success = [fetchedResultsController performFetch:&error];
if(!success) NSLog(#"error: %#", error);
self.resultsController = fetchedResultsController;
And back to the question.
How can one dynamically change the fetchLimit for an NSFetchedResultsController?
Any hits would be awesome!
Thanks!
Instand using setFetchLimit, using setBatchSize, see below for detail answer.
The count of the fetchedObjects array might not what you want to do, since it does not update the changes from the persistent store. From NSFetchedResultsController documentation:
The results array only includes instances of the entity specified by the fetch request (fetchRequest) and that match its predicate. (If the fetch request has no predicate, then the results array includes all instances of the entity specified by the fetch request.)
The results array reflects the in-memory state of managed objects in the controller’s managed object context, not their state in the persistent store. The returned array does not, however, update as managed objects are inserted, modified, or deleted.
If you only want to fetch 20 objects, set the fetch limit of the NSFetchRequest. If you want only to keep 20 objects in memory, use setBatchSize of the NSFetchRequest object.
figured this one out.
looks like I have to run performFetch: after I change the fetchLimit. :D
[self.resultsController.fetchRequest setFetchLimit:maxListItems];
[self.resultsController performFetch:&error];

Core Data, check for existing Many-To-Many relationship

Having trouble with my first Core Data project...
I have two entities in a many to many relationship: Quotes <<--->> Boilers.
When a user selects a Boiler in a UITableView row I want to know if that Boiler already has a relationship with the Quote that the page is managing, and toggle that relationship.
I think the code below fails because the Predicate doesn't know which Boiler has been selected, but I can't quite get this right...
- (void)managedObjectSelected:(NSManagedObject *)managedObject
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Boiler" inManagedObjectContext:managedObject.managedObjectContext]];
request.predicate = [NSPredicate predicateWithFormat:#"ANY myQuote = %#", quote];
NSError *error;
NSUInteger count = [managedObject.managedObjectContext countForFetchRequest:request error:&error];
if(count==0){
[quote addMyBoilersObject:(Boiler*) managedObject];
}
else {
[quote removeMyBoilersObject:(Boiler*) managedObject];
}
[managedObject.managedObjectContext save:&error];
}
Any help or pointers would be greatly appreciated...
One way to approach this is to use an NSFetchedResultsController to populate your UITableView. If you do this, then you should be able to toggle selection in
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Get the Boiler that was selected.
Boiler *boiler = (Experiment *) [fetchedResultsController objectAtIndexPath:indexPath];
if (boiler)
{
if ([[self quote] boilers] containsObject:boiler])
{
// connected, so disconnect
}
else
{
// disconnected, so connect
}
}
}
Anyway, have you tried something like this?

How to get all entries of a NSFetchedResultsController/NSManagedObjectContext?

I have a nice working iphone app that works with core data. I use a NSFetchedResultsController/NSManagedObjectContext as described in various tutorials.
Now I want to extends my app and add some more features. The problem I need to build up an array with objects that have informations from my data.
I somehow need to get a list of all data I have in my context.
I thought I could make an approach similar to the way I get the data for the UITableView.
id <NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
This one fails, because I do have actually multiple sections. Now I could go through all sections and make my own IndexPath to access my data with :
MyData *info = [_fetchedResultsController objectAtIndexPath:indexPath];
But I think there is another way I just have not found yet and I hope someone can help me out here.
Thanks a lot.
are you just looking for a method to get all objects from you NSFetchedResultsController? If so, use this.
NSArray *fetchedData = [_fetchedResultsController fetchedObjects];
if you have more than 1 entity build a fetchrequest for each entity. Something like this should give you all your objects.
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:self.entityName inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
If you wanted to get all of the objects for a particular section you could do this:
NSArray *sectionObjects = [_fetchedResultsController.sections[section_number] objects];

How to use the first character as a section name

I'm using Core Data for a table view, and I'd like to use the first letter of each of my results as the section header (so I can get the section index on the side). Is there a way to do this with the key path? Something like below, where I use name.firstLetter as the sectionNameKeyPath (unfortunately that doesn't work).
Do I have to grab the first letter of each result manually and create my sections like that? Is it better to put in a new property to just hold the first letter and use that as the sectionNameKeyPath?
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:#"name.firstLetter"
cacheName:#"Root"];
Thanks.
**EDIT: ** I'm not sure if it makes a difference, but my results are Japanese, sorted by Katakana. I want to use these Katakana as the section index.
You should just pass "name" as the sectionNameKeyPath. See this answer to the question "Core Data backed UITableView with indexing".
UPDATE
That solution only works if you only care about having the fast index title scroller. In that case, you would NOT display the section headers. See below for sample code.
Otherwise, I agree with refulgentis that a transient property is the best solution. Also, when creating the NSFetchedResultsController, the sectionNameKeyPath has this limitation:
If this key path is not the same as
that specified by the first sort
descriptor in fetchRequest, they must
generate the same relative orderings.
For example, the first sort descriptor
in fetchRequest might specify the key
for a persistent property;
sectionNameKeyPath might specify a key
for a transient property derived from
the persistent property.
Boilerplate UITableViewDataSource implementations using NSFetchedResultsController:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[fetchedResultsController sections] count];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [fetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
// Don't implement this since each "name" is its own section:
//- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
// return [sectionInfo name];
//}
UPDATE 2
For the new 'uppercaseFirstLetterOfName' transient property, add a new string attribute to the applicable entity in the model and check the "transient" box.
There are a few ways to implement the getter. If you are generating/creating subclasses, then you can add it in the subclass's implementation (.m) file.
Otherwise, you can create a category on NSManagedObject (I put this right at the top of my view controller's implementation file, but you can split it between a proper header and implementation file of its own):
#interface NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName;
#end
#implementation NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName {
[self willAccessValueForKey:#"uppercaseFirstLetterOfName"];
NSString *aString = [[self valueForKey:#"name"] uppercaseString];
// support UTF-16:
NSString *stringToReturn = [aString substringWithRange:[aString rangeOfComposedCharacterSequenceAtIndex:0]];
// OR no UTF-16 support:
//NSString *stringToReturn = [aString substringToIndex:1];
[self didAccessValueForKey:#"uppercaseFirstLetterOfName"];
return stringToReturn;
}
#end
Also, in this version, don't forget to pass 'uppercaseFirstLetterOfName' as the sectionNameKeyPath:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext
sectionNameKeyPath:#"uppercaseFirstLetterOfName" // this key defines the sections
cacheName:#"Root"];
And, to uncomment tableView:titleForHeaderInSection: in the UITableViewDataSource implementation:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo name];
}
There may be a more elegant way to do this, but I recently had the same problem and came up with this solution.
First, I defined a transient property on the objects I was indexing called firstLetterOfName, and wrote the getter into the .m file for the object.
e.x.
- (NSString *)uppercaseFirstLetterOfName {
[self willAccessValueForKey:#"uppercaseFirstLetterOfName"];
NSString *stringToReturn = [[self.name uppercaseString] substringToIndex:1];
[self didAccessValueForKey:#"uppercaseFirstLetterOfName"];
return stringToReturn;
}
Next, I set up my fetch request/entities to use this property.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:dataContext];
[request setEntity:entity];
[NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
Side note, apropos of nothing: Be careful with NSFetchedResultsController — it's not exactly fully baked yet IMHO, and any situation beyond the simple cases listed in the documentation, you will probably be better off doing it the 'old fashioned' way.
I solved this using the UILocalizedIndexCollation as mentioned in the NSFetchedResultsController v.s. UILocalizedIndexedCollation question
The elegant way is to do make the "firstLetter" a transient property, HOWEVER in practice that is slow.
It is slow because for a transient property to be calculated, the entire object needs to be faulted into memory. If you have a lot of records, it will be very, very slow.
The fast, but inelegant way, is to create a non-transient "firstLetter" property which you update each time you set your "name" property. Several ways to do this: override the "setName:" assessor, override "willSave", KVO.
See my answer to a similar question here, in which I describe how to create localized sectionKey indexes that are persisted (because you cannot sort on transient attributes in an NSFetchedResultsController).
Here is the simple solution for obj-c, several years and one language late . It works and works quickly in a project of mine.
First I created a category on NSString, naming the file NSString+Indexing. I wrote a method that returns the first letter of a string
#implementation NSString (Indexing)
- (NSString *)stringGroupByFirstInitial {
if (!self.length || self.length == 1)
return self;
return [self substringToIndex:1];
}
Then I used that method in the definition of the fetched result controller as follows
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"name.stringGroupByFirstInitial"
cacheName:nil];
The above code in conjunction with the two stringIndex methods will work with no need for messing about with transient properties or storing a separate attribute that just holds the first letter in core data
However when I try to do the same with Swift it throws an exception as it doesn't like having a function as part key path (or a calculated string property - I tried that too) If anyone out there knows how to achieve the same thing in Swift I would dearly like to know how.