CoreData NSFetchedResultsController count not working properly - ios

I have this fetchedResult method:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Cards" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Cards" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
But when I ask for self.fetchedResultsController.fetchedObjects.count it always returns me ONE more item than then it should. The first item is alway this: "<Cards: 0x83e9c50> (entity: Cards; id: 0x83e9c80 <x-coredata:///Cards/t8C371600-7875-468C-A529-C918F0008C384> ; data: {\n cardID = nil;\n favourite = 0;\n imageData = nil;\n})" but checking the sqlite via terminal I can see that this item ISN'T THERE. Any idea on what could it be?

It's difficult to understand what is going on. Your code looks good to me. But if you use the cache mechanism, this could be the strange behaviour you have.
To test, try to use nil instead of a cache in NSFetchedResultsController or delete the cache first and then do the request.
+ (void)deleteCacheWithName:(NSString *)name
If you pass nil all the cashes are deleted.
Let me know if you fix it.
P.S. I think that under the hood, cashes are saved and they can live among different app startups. Does anyone know about this?

Related

Strange bug: uncommenting an NSPredicate within an NSFetchedResultsController will empty the UITableView

I have a strange bug: if I uncomment my NSPredicate, the resulting UITableView is empty.
My data Model is the following:
Category <-->> Feed <-->> Post
I am fetching the Posts. Post.feed is a Post's Feed. Feed has an rss NString property.
Here's the code:
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (_fetchedResultsController == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post"
inManagedObjectContext:_globalMOC];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"feed.rss == %#", _detailItem.rss];
[fetchRequest setPredicate:predicate];
// 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:_globalMOC
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate.
// You should not use this function in a shipping application, although it may be useful
// during development. If it is not possible to recover from the error, display an alert
// panel that instructs the user to quit the application by pressing the Home button.
//
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
return _fetchedResultsController;
}
I only see results if I uncomment the NSPredicate. I tried with LIKE, ==, =, with double and single quotes around %#...
The best would be to directly compare the Feed object...
Anyone can help me?
Solved here.
The problem was that the NSFetchedResultsController was initialized once before the _detailItem was even set.

NSFetchedResultsController loads the whole data and bad performance

I have a large database with over 8000 entries in it. I want to load and list in tableview. I used NSFetchedResultsController. It's working fine except the bad performance. I found the tutorial here
And added my performFetch: method in the viewDidLoad: method. But it takes 2-3 seconds to load the view.
Here it is my code.
- (void)loadFetchResultsController {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Error occurs while fetching the foods with fetch results controller %#, %#", error, [error userInfo]);
exit(-1);
}
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (isSearching)
{
[NSFetchedResultsController deleteCacheWithName:#"SearchResults"];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Food" inManagedObjectContext:_managedObjectContext];
NSPredicate *hiddenPredicate = [NSPredicate predicateWithFormat:#"hidden == %#",[NSNumber numberWithBool:NO]];
if (isSearching)
{
hiddenPredicate = [NSPredicate predicateWithFormat:#"(hidden == %#) AND (name contains[cd] %# OR foodDescription contains[cd] %#)",[NSNumber numberWithBool:NO], searchTerm,searchTerm];
}
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:hiddenPredicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_managedObjectContext sectionNameKeyPath:#"name.stringGroupByFirstInitial" cacheName:(isSearching) ? #"SearchResults" : #"Root"];
_fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I used the instruments and found out that calling performFetch: method loads the whole data for the first time when viewDidLoad: calls.
My food data model is like this:
Any idea to improve the performance and get rid of delay.. Any help would be appreciated.
I believe you need to set the fetch limit on your fetch request, not just the batch size. Try calling [fetchRequest setFetchLimit:20]; and see if that helps. This should cause the first 20 records to be fetched. Then as the user scrolls through your data, make additional fetch requests to get more data.

How to edit nsfetchresults records when only some records are displayed?

I am trying to edit nsfetchresults records from another viewcontroller and it is working fine if all my database entries are displayed. But when I filter the table by date and try to edit it, I get this error
2013-03-22 23:09:02.895 easyDB[1079:907] * Assertion failure in
-[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2380.17/UITableView.m:1070 2013-03-22
23:09:18.865 easyDB[1079:907] caught exception!
NSInternalInconsistencyException
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (6) must be
equal to the number of rows contained in that section before the
update (2), plus or minus the number of rows inserted or deleted from
that section (0 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
I send it by
NSIndexPath *indexpath = (NSIndexPath*)sender;
GlucoseDetailViewController *glucoseDetailsVC = (GlucoseDetailViewController*)segue.destinationViewController;
GlucoseRecords *rec = [self.fetchedResultsController objectAtIndexPath:indexpath];
[glucoseDetailsVC setSentRecord:rec];
And in edit view
[self.sentRecord setGlucose:[NSNumber numberWithInt:[[txtGlucoseReading text] intValue]]];
[self.sentRecord setDate:self.dateToAdd];
[self.sentRecord setMealType:[NSString stringWithString:self.strMealOption]];
[self.sentRecord setQuickNote:[NSString stringWithString:self.strQuickNote]];
[self.sentRecord setDeleted:[NSNumber numberWithBool:NO]];
[self.sentRecord setWhoRecorded:[self.settings currentUser]];
NSError *error;
if (![[self.settings managedObjectContext] save:&error])
NSLog(#"Failed to add default user with error: %#", [error domain]);
FetchedResults Controller
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil && [self.strCurrentEntetyName isEqualToString:self.strEntetyName] &&(self.filteChanged==[NSNumber numberWithBool:NO])) {
return _fetchedResultsController;
}
self.filteChanged = [NSNumber numberWithBool:NO];
self.strCurrentEntetyName = [NSString stringWithString:self.strEntetyName];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[self strEntetyName]];
//NSEntityDescription *entity = [NSEntityDescription
// entityForName:#"Records" inManagedObjectContext:[self.settings managedObjectContext]];
if (self.isDateFilterSet==[NSNumber numberWithBool:NO])
{
fetchRequest.predicate = [NSPredicate predicateWithFormat:#" whoRecorded.username = %# AND deleted=0", [self.settings.currentUser username]];
}
else
{
fetchRequest.predicate = [NSPredicate predicateWithFormat:#" (whoRecorded.username = %#) AND (deleted = 0) AND (date =>%#) AND (date<=%#)", [self.settings.currentUser username],self.dateMinSetFilter ,self.dateMaxSetFilter];
}
NSLog(#"User:%#",[self.settings.currentUser username]);
// [fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:100];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[self.settings managedObjectContext] sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I understand what the problem is, but I am not able to see any good way to solve it. I can reset the table to display all the entries before editing but I don't think that's a good solution. Is there any better way to solve this problem?
Thanks
How did you 'display only some records'? Using NSPredicate to filter the result or anything else?
I think you need to locate the issue, is it because you operate one tableview from another view controller?
Try this code:
before calling parsing method please write: self.fetchedResultsController = nil;
- (NSFetchedResultsController *)fetchedResultsController // attaches an NSFetchRequest to this UITableViewController
{
if (_fetchedResultsController != nil )
{
return _fetchedResultsController;
}
NSLog(#"Length:: %d",[[_fetchedResultsController mutableCopy] length]);
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"YourEntityName"];
[fetchRequest setIncludesSubentities:YES];
// Sort by Application name
NSSortDescriptor* sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
//Sort by CommandNameloca
NSArray* sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:100];
tempArr=[[NSMutableArray alloc]initWithArray:sortDescriptors];
NSLog(#"****----- tempArr Count == %d",[tempArr count]);
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"date" cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
Please look in the log for array count. It will display the total record you get.
Please share your result. Best Luck.. !!
Found the answer!
[self.tableView updateConstraintsIfNeeded];
Needed to add this line to
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

NSFetchedResultsController not informed about model changes

Got somehow lost on something what worked in other projects and now stopped working.
I've got a controller (A) (subclass of NSObject) which has a NSFetchedResultsController for entities of class kClassCycleQuestionDetails.
My ManagedObjectContext is initiated with NSMainQueueConcurrencyType.
In another controller (B) I start inserting entities of the above class and start a save on every tenth object.
[self.moc performBlock:^{
[self prepareNewCycle];
}];
The context posts the NSManagedObjectContextDidSaveNotification (received in B) but I never see the controllers delegate method - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller called in A.
Both controller share the same managed object context, so I should see model changes?!
Has anyone an idea.
Edited:
For clarification: prepareNewCycle inserts a cycle entity into the context and than
adds entities of kClassCycleQuestionDetails to the cycle entity (to-many relationship).
Here's the code for the controller A's fetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
/*
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:kClassCycleQuestionDetails inManagedObjectContext:self.moc];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
[fetchRequest setPredicate:[self predicateForExamCycleMode:self.cycle.examQuestionModeValue cycleId:self.cycle.cycle_id]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kQCQDCycleQuestionDetails_id ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: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.moc
sectionNameKeyPath:nil
cacheName:NSStringFromClass([self class])];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[NSFetchedResultsController deleteCacheWithName:NSStringFromClass([self class])];
NSError *error = nil;
if (![_fetchedResultsController performFetch:&error]) {
ALog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}

Issue with NSPredicate in fetchedResultsController method

I'm creating an app that lets a salesperson order stock for their customers from their iPhone.
The user navigates to a customer and creates an order. A blank tableview appears and the user then adds items to the tableview by selecting them from an inventory screen.
When they add an item to the order, the navigation controller pops the view and shows the order view again. The user should only be able to see orders for that customer.
I originally built the app entirely in sqlite and I achieved this by using the query
SELECT PRODUCT FROM TRANSLINE WHERE CUSTOMERACCNO = ?
I have now moved onto Core Data and I need to achieve the same functionality. I'm trying to implement this behaviour in the fetchedResultsController method using NSPredicate, but I can't seem to get it working - all I get is a blank screen. However, when I don't implement it, I get ALL orders i.e. orders for every customer, not just this one.
Here's my code :
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Configure the Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TransLine" inManagedObjectContext:__managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
//Configure the predicate
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"customerAccountNo == %#", _customerAccountNo];
[fetchRequest setPredicate:predicate];
//Configure Sort Descriptors
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"PRODAC" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:__managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
return __fetchedResultsController;
}
Thanks in advance for all your help.
Well, it seems that in my frustration.... I am, in fact, an idiot. Guess what I forgot to include.
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}

Resources