Objective C - Fetch Results Controller fetching less objects after insert - ios

I have a chat panel in an iOS application using NSFetchResultController. Fetching occurs as:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
DataSource *dataSource = [DataSource getInstance];
[NSFetchedResultsController deleteCacheWithName:#"MessageList"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:_CHAT_MESSAGE_ENTITY_ inManagedObjectContext:dataSource.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
//Predicate
DataClass *dataClass = [DataClass getInstance];
NSPredicate *predicate;
if ([self.chatType intValue] == SINGULAR_CHAT_TYPE) {
Contact *receiver = [self.members objectAtIndex:0];
predicate = [NSPredicate predicateWithFormat:#"(senderUid LIKE %# AND receiverUid LIKE %#) OR (senderUid LIKE %# AND receiverUid LIKE %#)", [dataClass getKeychainValueWithKey:_USER_ID_IDENTIFIER_], receiver.contactUid, receiver.contactUid, [dataClass getKeychainValueWithKey:_USER_ID_IDENTIFIER_]];
}
else { // group chat
}
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"messageDate" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:dataSource.managedObjectContext sectionNameKeyPath:#"messageId" cacheName:#"MessageList"];
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 in fetchedResultsController performFetch %#List, %#", _CHAT_MESSAGE_ENTITY_,error, [error userInfo]);
//abort();
}
return _fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.chatTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.chatTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.chatTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
return;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.chatTableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
// case NSFetchedResultsChangeUpdate:
// [self configureCell:[chatTableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
// break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.chatTableView reloadData];
[self.chatTableView endUpdates];
}
This fetching works normal, when I open the view controller all previous messages are seen on the table as they should be. However, when I type a new message and press "send" button, not only the new message but also last message before it is not getting fetched.
I get the number of rows, see there are 2 missing objects.
Still, if I stop app and run again, these last 2 messages are getting fetched and shown.
The same problem is occuring conversations panel. In other words, after sending message my conversation view controller needs to be updated. However, its last conversations (chosen one) is getting lost.
I'm saving the message and conversations as:
DataSource *ds = [DataSource getInstance];
ChatMessage *newMsg = (ChatMessage*)[ds createInsertObject:_CHAT_MESSAGE_ENTITY_];
[newMsg loadFromDictionary:tmpDict];
Chat *chat = (Chat*)[ds findOrCreateWithIdentifier:[[headers objectForKey:CHAT_TYPE] stringValue]:[headers objectForKey:#"receiverUid"] :_CHAT_ENTITY_];
[chat loadHeaders:headers andMessage:newMsg];
newMsg.chat = chat;
[ds coreDataSaveContext];
And after this saving, I'm telling to my chat view controller:
- (void)refreshPage {
[self.fetchedResultsController performFetch:nil];
[self.chatTableView reloadData];
[self takeTableContentsUp];
}
Am I missing something about fetch result controller? Why is this problem happening and how can I fix it?
Thank you!
EDIT: If I remove "Chat" entity in saving message, just save the message everyting is ok for chat panel. Thus the problem is multiple entities interacted with each other.
How should i edit and save 2 entities to core data, and fetch without problem?

Related

NSfetchedResultsController changes not reflecting to UI

Problem with NSFetchedResultsController is that I fetched the data and it populates the UITableView with cacheName set to nil. Later when I change the predicate of NSFetchedResultsController and called perfromFetch, it won't refreshes the UITableView however the data inside NSFetchedResultsController is updated. One thing more, NSFetchedResultsControllerDelegate methods are also not invoking.
Thanks in Advance.
Edited: Added Code
NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate:
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSManagedObjectContext *context = [GmailDBService sharedService].managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Thread"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"historyId" ascending:NO];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(messages, $m, ANY $m.labels.identifier == %#).#count > 0", self.label.identifier];
fetchRequest.sortDescriptors = #[sortDescriptor];
fetchRequest.fetchBatchSize = 20;
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
[_fetchedResultsController performFetch:nil];
return _fetchedResultsController;
}
Predicate Refreshing
- (IBAction)unwindToThreadsController:(UIStoryboardSegue *)segue
{
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:#"identifier == %#", #"15f1919682399cc9"];
[self.fetchedResultsController performFetch:nil];
}
fetchedResultsController are not expensive. You should not be afraid to create and destroy them as needed. When you need to change the predicate discard the current fetchedResultsController and create a new one. Don't forget to reload the tableView after you do so.
The changes aren't triggering the fetchedResultsController because you are monitoring threads, but your predicate is based on messages. So when a message is changed it does not trigger a change in the fetchedResultsController. The controller only monitors changes to one entity and does not expect changes to other entities to effect it. You can fix this in a few ways:
setup your fetchedResultsController to look at message and group them by threads, then only display every section (ie thread) as a row.
every time you change a message also change its thread (message.thread.threadId = message.thread.threadId will count as a change as far as the fetchedResultsController is concerned).
Also fetchBatchSize isn't respected by a fetchedResultsController so you should remove it for clarity.

NSFetchedResultsController indexPathForObject not counting sections

I am updating a download view/button on a cell, and when I go to update my cell, I am not getting the correct section.
My code to get the index and update the download progress is this:
Object *obj = (Object *)notification.object;
NSIndexPath *index = [self.fetchedResultsController indexPathForObject:obj];
MyTableViewCell *cell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:index];
DownloadProgressButtonView *buttonView = (DownloadProgressButtonView *)cell.accessoryView;
NSNumber *progressLong = [notification.userInfo objectForKey:#"progress"];
float progress = [progressLong floatValue];
NSNumber *totalBytesLong = [notification.userInfo objectForKey:#"totalBytes"];
float totalBytes = [totalBytesLong floatValue];
buttonView.progress = progress *.01;
float totalDownloadEstimate = totalBytes / 1.0e6;
float megaBytesDownloaded = (progress *.01) * totalDownloadEstimate;
cell.bottomLabel.text = [NSString stringWithFormat:#"%.1f MB of %.1f MB", megaBytesDownloaded, totalDownloadEstimate];
If I have two objects, each in a different section, they have the same row (0). When I go to update my cell, it updates the cell in section 1 instead of section 0. How do I fix this?
I can put whatever other code is needed. It works perfectly if I just disable sections in my NSFetchedResultsController.
My NSFetchedResultsController and delegates.
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *nameString = [[NSSortDescriptor alloc] initWithKey:self.sectionSortDescriptor ascending:NO];
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:self.sortDescriptor ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:nameString,descriptor, nil]];
NSString *downloadStartedString = #"Preparing to download";
NSString *downloadingString = #"Downloading";
NSString *downloadPausedString = #"Download paused";
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"(downloaded == YES) OR (downloadStatus like[cd] %#) OR (downloadStatus like[cd] %#) OR (downloadStatus like[cd]%#)",downloadPausedString, downloadStartedString,downloadingString];
[fetchRequest setFetchBatchSize:20];
_fetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:self.sectionNameString
cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;
return _fetchedResultsController;
}
/*
NSFetchedResultsController delegate methods to respond to additions, removals and so on.
*/
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:(StudioTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeMove:
NSLog(#"A table item was moved");
break;
case NSFetchedResultsChangeUpdate:
NSLog(#"A table item was updated");
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
Finally when the download status changes, I update the object and send a notification to update the cell with the new status:
- (void)updateCell:(NSNotification *)notification
{
Object *obj = (Object *)notification.object;
NSIndexPath *index = [self.fetchedResultsController indexPathForObject:obj];
[self.tableView reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationFade];
}
Updating cells this way is not reliable. A cell updated in such a way will sooner or later be reused. The cell's subviews will be re-configured by tableView:cellForRowAtIndexPath:, based on the data provided by the datasource.
You should make changes to the Object itself (instead of passing them in notification's userInfo) and save the managed object context. Then NSFetchedResultsControllerDelegate callbacks will fire, allowing you to reload the corresponding row. Then you should set all the properties of MyTableViewCell in configureCell:atIndexPath.
And the configureCell: method should be called from cellForRowAtIndexPath method, not from the fetched results controller delegate method. The general pattern is to call reloadRowsAtIndexPaths: in controllerDidChangeObject:. Otherwise you can run into some cell reuse issues.
An idea on how the code should look like:
- (void)updateCell:(NSNotification *)notification
{
//depending on your Core Data contexts setup,
// you may need embed the code below in performBlock: on object's context,
// I omitted it for clarity
Object *obj = (Object *)notification.object;
//save changes to the object, for example:
NSNumber *progressLong = [notification.userInfo objectForKey:#"progress"];
obj.progress = progressLong;
//set all the properties you will need in configureCell:, then save context
[obj.magagedObjectContext save:&someError];
}
then the fetched results controller will call controllerDidChangeObject:, in this method you should reload the row:
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
finally, configure the cell (let's assume that you call configureCell:atIndexPath from tableView:cellForRowAtIndexPath:):
- (void)configureCell:(MyTableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath {
Object *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
DownloadProgressButtonView *buttonView = (DownloadProgressButtonView*) cell.accessoryView;
buttonView.progress = object.progress.floatValue *.01;
//and so on
}

Having Trouble Reading From Core Data

I have an array that the user can add objects to. I have a table view that lists "bad" ingredients the user does not want in their food. They can add these objects in an array, but I don't think Im reading them properly. I know for sure I'm writing properly because I make sure that my code checks for it.
This is how I add objects in Core Data:
-(void)addRow
{
UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:#"Add a Bad Ingredient" message:#"Type the name of the ingredient" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:#"Cancel", nil];
myAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[myAlertView show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
NSManagedObjectContext *context = [self managedObjectContext];
AllergicIngredient *allergic = [NSEntityDescription insertNewObjectForEntityForName:#"AllergicIngredient" inManagedObjectContext:context];
NSString *enteredString = [[alertView textFieldAtIndex:0] text];
[allergic setValue:enteredString forKey:#"name"];
NSError *error;
if (![context save:&error])
{
NSLog(#"Couldnt find the save %#", error.localizedDescription);
}
else
{
NSLog(#"It saved properly");
}
[badIngredientsArray addObject:enteredString];
NSLog(#"%#", badIngredientsArray);
[self.tableView reloadData];
}
This is how I read from it (Making sure my array is getting Objects from core Data):
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"AllergicIngredient"];
badIngredientsArray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
I'm struggling with finding the errors in how I get read from it. So far, I'm not given any error messages or SIGABRTS, because the app just crashed when I try to go to the specific page where I'm fetching the data.
I see you're using a UITableView with Core Data.
Given the context, why don't you use an NSFetchResultsController?
If you use that, you will then be able to perform the following:
#pragma mark - Fetched Results Controller Section
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[MyMO description]
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Specify how the fetched objects should be sorted
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#""
ascending:YES
selector:#selector(localizedStandardCompare:)];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
[fetchRequest setFetchBatchSize:20];
NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Error Fetching: %#", error);
}
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"masterCache"];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch (type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
Person *changedPerson = [self.fetchedResultsController objectAtIndexPath:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.text = changedPerson.birthName;
}
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch (type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
break;
}
}
And UITableView methods:
- (void)viewDidLoad
{
[super viewDidLoad];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Error while fetching: %#", error);
abort();
}
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [self.fetchedResultsController.sections count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
id <NSFetchedResultsSectionInfo> sectionInfo = self.fetchedResultsController.sections[section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
MyMo *mo = [self.fetchedResultsController objectAtIndexPath:indexPath];
// Configure the cell...
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [self.fetchedResultsController.sections[section]name];
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectContext *context = self.managedObjectContext;
MyMo *mo = [self.fetchedResultsController objectAtIndexPath:indexPath];
[context deleteObject:mo];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Error saving: %#", error);
}
}
}

Problems after adding NSFetchedResultsController to a project

I set up CoreData without NSFetchedResultsController and had everything saving fine. After switching to NSFetchedResultsController, I am getting a strange error when trying to save the image.
Here is the code I am using to save the image:
- (void)saveImage {
NSManagedObjectContext *context = [self managedObjectContext];
TimeTravelFeed *timeTravelFeed = [NSEntityDescription insertNewObjectForEntityForName:#"TimeTravelFeed" inManagedObjectContext:context];
NSData *imageData = UIImageJPEGRepresentation(self.thumbImage, 0.8f);
[timeTravelFeed setValue:imageData forKey:#"imageData"];
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//[tableView reloadData];
}
And here is the error message:
-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40
2013-12-08 10:09:49.442 Time Travel[830:60b] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.
-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40 with userInfo (null)
2013-12-08 10:09:49.443 Time Travel[830:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40'
Here is the code for NSFetchedResultsController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailsViewController = [[DetailViewController alloc] init];
[self.navigationController pushViewController:detailsViewController animated:YES];
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"TimeTravelFeed" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"imageData" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
The problem seems to be here:
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"imageData" ascending:NO];
You cannot sort on a "Binary Data" attribute. A fetched results controller
needs a sort descriptor, so you should use a different attribute, e.g. a string, number or date. For example
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"creationDate" ascending:NO];
where "creationDate" is an attribute (type "Date") and set when the object is created:
[timeTravelFeed setValue:imageData forKey:#"imageData"];
[timeTravelFeed setValue:[NSDate date] forKey:#"creationDate"];
Go to your DBfile, select entity named "TimeTravelFeed" and select key "imageData". Set properties.

NSFetchedResultsController delegate fires methods only on first app launch

Sorry for my terrible english.
I have big problem in my iOS app. Application has big database which is managed by Core Data. And I have many TableView Controllers for displaying this data. Any change in database should be shown in tableview. It can be reached by implementing NSFetchedResultsController delegate protocol. All realization are very simple like in books. If application starts in simulator first time and I add new entries in some tables next delegate methods are successfully fired:
– controllerWillChangeContent:
– controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
– controllerDidChangeContent:
After stop debugging and start application again none of listed methods are fired.
They calls only after [managedObjectContext save] operation will perform.
Have you any ideas why this happens?
Source code:
//IssueProfileViewController.h class implements NSFetchedResultController delegate methods
- (NSFetchedResultsController*)fetchedResultsController{
if (_fetchedResultsController == nil) {
NSManagedObjectContext *managedObjectContext = self.issue.managedObjectContext;
NSFetchRequest *aFetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"IssueHistoryItem" inManagedObjectContext:managedObjectContext];
[aFetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
//NSPredicate *predicate = [[NSPredicate predicateWithFormat:#"issue == %# && isComment == NO", self.issue] retain];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"issue == %# && isComment == NO", self.issue];
[aFetchRequest setSortDescriptors:sortDescriptors];
[aFetchRequest setPredicate:predicate];
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:aFetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[aFetchRequest release];
//[predicate release];
[sortDescriptors release];
[sortDescriptor release];
_fetchedResultsController = aFetchedResultsController;
_fetchedResultsController.delegate = self;
}
return _fetchedResultsController;
}
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
-(void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
NSArray *paths;
// NSIndexSet *section = [NSIndexSet indexSetWithIndex:[newIndexPath section]];
NSIndexPath *cellContentIndexPath;
switch (type) {
case NSFetchedResultsChangeInsert:
// paths = [NSArray arrayWithObject:newIndexPath];
if (![anObject isKindOfClass:[ChangeIssueDimensionValueHistoryItem class]]) {
cellContentIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:newIndexPath.section];
paths = [NSArray arrayWithObject:cellContentIndexPath];
[self.tableView insertRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationFade];
[self sendMessageAboutObjectsCountChanged];
}
break;
case NSFetchedResultsChangeDelete:
paths = [NSArray arrayWithObject:indexPath];
[self.tableView deleteRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationFade];
[self sendMessageAboutObjectsCountChanged];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[self.tableView cellForRowAtIndexPath:indexPath]
withIssueHistoryItem:[self.fetchedResultsController objectAtIndexPath:indexPath]];
break;
default:
break;
}
}
That seems to be the proper way that the NSFetchedResultsController performs--responding to changes at the model layer (i.e. [managedObjectContext save]).
In the documentation: https://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html under 'Responding to Changes' it states that the controller will not show changes until the managed object's context has received a processPendingChanges message. That message can be triggered manually, also.

Resources