NSFetchedResultsController with data not updating - iphone

I'm making an app which fetches a list of products from a server. I then store them in a Core Data database and I present them using GMGridView and the datasource is a NSFetchedResultsController. When I change the product details in the server, I want my iOS app to synchronize and make the necessary changes so I implement the NSFetchedResultsControllerDelegate method. How should I update my gridView properly?
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[_currentData insertObject:anObject atIndex:newIndexPath.row];
[_currentData removeObjectAtIndex:indexPath.row];
[_gmGridView insertObjectAtIndex:newIndexPath.row animated:YES];
[_gmGridView removeObjectAtIndex:indexPath.row animated:YES];
[_gmGridView reloadObjectAtIndex:newIndexPath.row animated:YES];
[_gmGridView reloadObjectAtIndex:indexPath.row animated:YES];
[_gmGridView reloadData];
//[self updatePageControl];
break;
case NSFetchedResultsChangeDelete:
[_currentData removeObjectAtIndex:indexPath.row];
[_gmGridView removeObjectAtIndex:indexPath.row animated:YES];
//[self updatePageControl];
[_gmGridView reloadInputViews];
[_gmGridView reloadData];
break;
case NSFetchedResultsChangeUpdate:
[_gmGridView reloadObjectAtIndex:indexPath.row animated:YES];
////////might be irrelevant, but just trying it out
[_currentData insertObject:anObject atIndex:newIndexPath.row];
[_currentData removeObjectAtIndex:indexPath.row];
[_gmGridView insertObjectAtIndex:newIndexPath.row animated:YES];
[_gmGridView removeObjectAtIndex:indexPath.row animated:YES];
[_gmGridView reloadObjectAtIndex:newIndexPath.row animated:YES];
[_gmGridView reloadObjectAtIndex:indexPath.row animated:YES];
////////
[_gmGridView reloadInputViews];
[_gmGridView reloadData];
break;
case NSFetchedResultsChangeMove:
[_currentData removeObjectAtIndex:indexPath.row];
[_currentData insertObject:anObject atIndex:newIndexPath.row];
[_gmGridView removeObjectAtIndex:indexPath.row animated:YES];
[_gmGridView insertObjectAtIndex:newIndexPath.row animated:YES];
[_gmGridView reloadInputViews];
[_gmGridView reloadData];
break;
}
}
Some details:
_currentData = [[self.fetchedResultsController fetchedObjects]mutableCopy];
//I did this because previously I wasn't using a fetchedResultsController but a NSMutableArray instead. I know that it's inefficient (because I have 2 models) but this is the simplest implementation I want to do now.
I make the CoreData changes in a different class by modifying the same UIManagedDocument alloc,init-ed from the same local URL.
However, I get 2 important problems:
The items in the database are updated (eg. change of product name or price) but the changes are not reflected in the UI, ie I can't reload my GMGridViewCell properly. (take note of the code related to reloading above)
Most of the time, the products I update in the server are duplicated in the database although I have a mechanism to prevent such an error. (Before I create a product, I first search for an existing one using a unique identifier. If there is an existing product, I just modify its details). Here's the code:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Product"];
request.predicate = [NSPredicate predicateWithFormat:#"product_id = %#", [imonggoInfo objectForKey:PRODUCT_ID]];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
request.sortDescriptors = [NSArray
arrayWithObject:sortDescriptor]; NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches | ([matches count] > 1)){
//handle error
}else if ([matches count] == 0){
//make a new product
}else{
//return existing
item = [matches lastObject];
}

Turns out I have to listen to NSManagedObjectContextDidSaveNotification and I have to merge the changes in the context modified in the background thread and the one in the main thread. I got the idea from the chosen answer in this question
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
//Then the changes in the context has to be merged
- (void)contextDidSave:(NSNotification*)notification{
NSLog(#"contextDidSave Notification fired.");
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[self.itemDatabase.managedObjectContext performSelectorOnMainThread:selector withObject:notification waitUntilDone:NO];
}

Related

load UICollectionView from core data

I'm developing an iOS app and at a certain point I store images draw by the user in Core Data.
Now I would like to load all the recorded images in a UICollectionView so the user could select one and share it on social networks.
Everything in my app works except this last part.
I followed all sorts of tutorials on the net but all examples I could find on UICollectionView were using images from Flickr or similar websites.
Here is my code at this point:
#import "LibraryViewController.h" (The name of the class we're in)
#import "SocialViewController.h"
#import "CollectionViewCell.h"
static NSString *cellIdentifier = #"MemeCell";
#implementation LibraryViewController {
NSMutableArray *_objectChanges;
NSMutableArray *_sectionChanges;
}
#pragma mark - View Controller LifeCycle
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_objectChanges = [NSMutableArray array];
_sectionChanges = [NSMutableArray array];
self.title = #"Meme collection";
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"share"]) {
NSIndexPath *indexPath = [[self.collectionView indexPathsForSelectedItems] lastObject];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
SocialViewController *destViewController = segue.destinationViewController;
[destViewController setDetailItem:object];
}
}
#pragma mark - UICollectionView
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = (CollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier
forIndexPath:indexPath];
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
[cell setImage:[UIImage imageWithData:[object valueForKey:#"image"]]];
return cell;
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Meme" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
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;
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
NSMutableDictionary *change = [NSMutableDictionary new];
switch(type) {
case NSFetchedResultsChangeInsert:
change[#(type)] = #(sectionIndex);
break;
case NSFetchedResultsChangeDelete:
change[#(type)] = #(sectionIndex);
break;
}
[_sectionChanges addObject:change];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
NSMutableDictionary *change = [NSMutableDictionary new];
switch(type)
{
case NSFetchedResultsChangeInsert:
change[#(type)] = newIndexPath;
break;
case NSFetchedResultsChangeDelete:
change[#(type)] = indexPath;
break;
case NSFetchedResultsChangeUpdate:
change[#(type)] = indexPath;
break;
case NSFetchedResultsChangeMove:
change[#(type)] = #[indexPath, newIndexPath];
break;
}
[_objectChanges addObject:change];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if ([_sectionChanges count] > 0)
{
[self.collectionView performBatchUpdates:^{
for (NSDictionary *change in _sectionChanges)
{
[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {
NSFetchedResultsChangeType type = [key unsignedIntegerValue];
switch (type)
{
case NSFetchedResultsChangeInsert:
[self.collectionView insertSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];
break;
case NSFetchedResultsChangeDelete:
[self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];
break;
case NSFetchedResultsChangeUpdate:
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];
break;
}
}];
}
} completion:nil];
}
if ([_objectChanges count] > 0 && [_sectionChanges count] == 0)
{
[self.collectionView performBatchUpdates:^{
for (NSDictionary *change in _objectChanges)
{
[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {
NSFetchedResultsChangeType type = [key unsignedIntegerValue];
switch (type)
{
case NSFetchedResultsChangeInsert:
[self.collectionView insertItemsAtIndexPaths:#[obj]];
break;
case NSFetchedResultsChangeDelete:
[self.collectionView deleteItemsAtIndexPaths:#[obj]];
break;
case NSFetchedResultsChangeUpdate:
[self.collectionView reloadItemsAtIndexPaths:#[obj]];
break;
case NSFetchedResultsChangeMove:
[self.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]];
break;
}
}];
}
} completion:nil];
}
[_sectionChanges removeAllObjects];
[_objectChanges removeAllObjects];
}
#end
When I run and try to access this ViewController, the app crashes with this error message:
2013-07-25 23:08:11.832 MemeGen[87259:c07] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Meme''
* First throw call stack:
and it points to this line of code:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Meme" inManagedObjectContext:self.managedObjectContext];
Obviously the self.managedObjectContext is nil.
How should I assign it? And when the user launches the application for the first time and the core data is empty, how should I manage it ? Forbidding its access as long as there are no images?
Otherwise, I know how to store the draw images on the file system. If someone knew a way to load it in the UICollectionView it could also be a solution I could accept.
I would not recommend storing images in core data unless you are using the external storage functionality. Large binary objects are rarely something you want to store inside something like core data. You can, of course, store filesystem paths or URLs on core data objects that point to images on the filesystem.
To use Core Data with a UICollectionView, take a look at NSFetchedResultsController.
Here's an example of using the two together to get you started.
You will want to get a reference to your ManagedObjectContext. Oftentimes this gets created in your application delegate. In which case you will want something like this -
NSManagedObjectContext *aManagedObjectContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).managedObjectContext;
If you want you can do this in your viewDidLoad and assign it to self.managedObjectContext.

NSFetchedResultsController not updating?

When my user sees a picture, she can like it by pressing a button. The following code is run:
- (void)feedback:(Item *)item isLiked:(bool)liked {
// Update the item with the new score asynchornously
NSManagedObjectID *itemId = item.objectID;
dispatch_async(dispatch_get_main_queue(), ^{
// Create a new managed object context and set its persistent store coordinator
// Note that this **must** be done here because this context belongs to another thread
AppDelegate *theDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
[localContext setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];
Item *localItem = (Item *)[localContext objectWithID:itemId];
localItem.liked = [NSNumber numberWithBool:liked];
localItem.updated_at = [NSDate date];
NSError *error;
if (![localContext save:&error]) {
NSLog(#"Error saving: %#", [error localizedDescription]);
}
});
In my app, LikedViewController shows the images that the user has liked. LikedVC consists of a UITableViewController hooked up to an NSFetchedResultsController.
LikedVC:
- (void)viewDidLoad
{
[super viewDidLoad];
// NSFetchedResultsController
NSManagedObjectContext *moc = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
_fetchedResultsController = \
[[NSFetchedResultsController alloc] initWithFetchRequest:[self.delegate getFetchRequest]
managedObjectContext:moc
sectionNameKeyPath:nil
cacheName:nil]; // TODO investigate whether we should bother with cache
_fetchedResultsController.delegate = self;
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
// Bottom loading bar
self.tableView.tableFooterView = self.footerView;
self.footerActivityIndicator.hidesWhenStopped = true;
// ActivityIndicator
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.color = [UIColor blackColor];
[self.tableView addSubview:self.activityIndicator];
self.activityIndicator.hidesWhenStopped = true;
// FIXME Unable to center it inside the tableView properly
self.activityIndicator.center = CGPointMake(self.tableView.center.x, self.tableView.center.y - self.tabBarController.tabBar.frame.size.height);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Automatically fetch when there is nothing in the UITableView
if ([self tableView:self.tableView numberOfRowsInSection:0] == 0) {
if ([self canFetch]) {
[self refill];
}
}
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
if (self.operation && self.operation.isExecuting) {
NSLog(#"Cancelling Operation: %#", self.operation);
[self.operation cancel];
self.isFetching = false;
}
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"FeedCell";
Item *item = [_fetchedResultsController objectAtIndexPath:indexPath];
FeedCell *cell = (FeedCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.item = item;
cell.tag = indexPath.row;
cell.customImageView.userInteractionEnabled = YES;
// NOTE Don't try to do this at the UITableViewCell level since the tap will be eaten by the UITableView/ScrollView
if (self.likeOnTap) {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
tap.numberOfTapsRequired = 1;
[cell.customImageView addGestureRecognizer:tap];
}
// Set up the buttons
[cell.likeButton addTarget:self action:#selector(liked:) forControlEvents:UIControlEventTouchUpInside];
[cell.dislikeButton addTarget:self action:#selector(disliked:) forControlEvents:UIControlEventTouchUpInside];
[cell.detailButton addTarget:self action:#selector(detailed:) forControlEvents:UIControlEventTouchUpInside];
[[SDImageCache sharedImageCache] queryDiskCacheForKey:item.image_url done:^(UIImage *image, SDImageCacheType type) {
if (image) {
[cell setCustomImage:image];
} else {
// If we have to download, make sure user is on the image for more than 0.25s before we
// try to fetch. This prevents mass downloading when the user is scrolling really fast
double delayInSeconds = 0.25;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if ([self isIndexPathVisible:indexPath]) {
[SDWebImageDownloader.sharedDownloader
downloadImageWithURL:[NSURL URLWithString:item.image_url]
options:0
progress:^(NSUInteger receivedSize, long long expectedSize) { }
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
[cell setCustomImage:image];
[[SDImageCache sharedImageCache] storeImage:image forKey:item.image_url];
}
}];
}
});
}
}];
// Check if we are almost at the end of the scroll. If so, start fetching.
// Doing this here is better than overriding scrollViewDidEndDragging
if (indexPath.row >= [self.tableView numberOfRowsInSection:0] - 3) {
[self refill];
}
return cell;
}
#pragma mark - Table view delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id sectionInfo = [_fetchedResultsController.sections objectAtIndex:section];
NSInteger ret = [sectionInfo numberOfObjects];
self.hasContent = (ret != 0);
return ret;
}
# pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
NSLog(#"1");
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
NSLog(#"2");
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 {
NSLog(#"3");
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:
[NSException raise:#"Unknown update" format:#"NSFetchedResultsChangeUpdate: invoked"];
// [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[NSException raise:#"Unknown update" format:#"NSFetchedResultsChangeMove: invoked"];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
NSLog(#"4");
[self.tableView endUpdates];
}
(Note that I have omitted some info to keep this question short)
This is the fetchRequest of LikedVC
- (NSFetchRequest *)getFetchRequest {
NSManagedObjectContext *moc = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:moc];
[request setPredicate:[NSPredicate predicateWithFormat:#"liked == %d OR origin == %d", 1, OriginsLike]];
[request setEntity:entity];
[request setResultType:NSManagedObjectResultType];
[request setFetchBatchSize:10];
NSSortDescriptor *d = [[NSSortDescriptor alloc] initWithKey:#"updated_at" ascending:NO selector:nil];
[request setSortDescriptors:[NSArray arrayWithObject:d]];
return request;
}
I am seeing a bug where the user has liked a item, but then when the user switches over to LikedVC the item is not shown anywhere.
I added NSLog(#"1"), NSLog(#"2"), ... in the tableView's controllerDidChangeContent, controllerWillChangeContent, etc methods. I do not see "1", "2", .. being logged at all.
Why is my NSFetchedResultsController not working?
Direct Answer
The direct answer to your question is you need to observe NSManagedObjectContextDidSaveNotification and merge the changes from your temporary context into the main context that the NSFetchedResultsController is observing. It's best to do this in whatever object owns your main context. Looks like you're using your app delegate.
You merged the data as follows:
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
// if the notification is on a background thread, forward it to the main thread
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(managedObjectContextDidSave:) withObject:notification];
return;
}
// if a context other than the main context has saved, merge the changes
if (notification.object != self.managedObjectContext) {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
}
Your code seems suspect
All of your logic happens on the main queue so you don't really need another context at all. There's also no real benefit to using dispatch_async on the main queue in this case. If you keep everything on the main thread just create and save the new object directly into the main context. If you really do want things to be asynchronous, the use dispatch_async to dispatch the block to a background queue and then you would need a new context (as you have implemented) for the background thread.

Getting An Assertion Failure Error

I am getting the following error when loading one of my views that has a UITableView in it. Does anyone know how to fix it? I have already tried deleting the (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath method, but that didn't help.
I'm thinking it has to do with the table's delegate not being updated properly with numberOfRowsInSection or something.
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-1448.89/UITableView.m:995
2011-06-05 00:38:12.116 App[14523:707] Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (0 inserted, 3 deleted). with userInfo (null)
Here is my code:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
NSLog(#"fetched results : \n%#\n",[self.fetchedResultsController fetchedObjects]);
// Commit the change.
NSError *error = nil;
// Update the array and table view.
if (![managedObjectContext save:&error])
{
// Handle the error.
}
//[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Set" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"sets == %#", self.exercise]];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" 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.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
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();
}
NSLog(#"fetched results : \n%#\n",[self.fetchedResultsController fetchedObjects]);
NSLog(#"fetch count: %i", [fetchedResultsController.fetchedObjects count]);
return self.fetchedResultsController;
}
#pragma mark - Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.setsTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.setsTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.setsTableView 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.setsTableView;
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.setsTableView endUpdates];
[self.setsTableView reloadData];
}
is Try uncommenting [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
Please note:
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
should return a value 1 less than that of before calling 'deleteRows..'
Basic problem is this: You are instructing UI to delete one of the rows, but not removing that row from the back end.
If you are using the code from CoreDataBooks sample you might want to change this:
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
}
with
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
});
}
This will prevent NSFetchedResultsController from refreshing while the tableView is not visible because of the UINavigationController transition.
Of course this thing occurs only in simulator, because on the device the insert will take longer than the [self dismissModalViewControllerAnimated:YES] animation.
In my case when I called [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES]; only, I got this type of error. I solved this by removing the object from Array (from this array I am displaying data.) and called [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
I put each section elements in separated arrays. Then put them into another array( arrayWithArray). My solution here for this problem:
[quarantineMessages removeObject : message];
[_tableView beginUpdates];
if([[arrayWithArray objectAtIndex: indPath.section] count] > 1)
{
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indPath] withRowAnimation:UITableViewRowAnimationBottom];
}
else
{
[_tableView deleteSections:[NSIndexSet indexSetWithIndex:indPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
[_tableView endUpdates];

What Does This NSZombie Error Message Mean?

I turned zombies on because I was getting some crashes. Now I' getting this error in console. Does anyone know what it means?
*** -[RoutineDayTableViewController retain]: message sent to deallocated instance 0x7464150
#implementation RoutineDayTableViewController
#synthesize fetchedResultsController;
#synthesize exerciseChooserView;
#synthesize routineTableView;
#synthesize managedObjectContext;
#synthesize selectedExercise;
#synthesize theSelectedRoutine;
- (void)dealloc
{
NSLog(#"dealloc");
[fetchedResultsController release];
[selectedExercise release];
[managedObjectContext release];
[exerciseChooserView release];
[routineTableView release];
[theSelectedRoutine release];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.routineTableView.delegate = self;
if (managedObjectContext == nil)
{
managedObjectContext = [(CurlAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
[managedObjectContext retain];
}
}
- (void)viewDidUnload
{
NSLog(#"viewDidUnload");
[super viewDidUnload];
self.exerciseChooserView = nil;
self.routineTableView = nil;
self.fetchedResultsController = nil;
}
#pragma mark - Exercise Editing
-(IBAction)exerciseChooser
{
RoutineExerciseChooserViewController *routineExerciseChooserViewController = [[[RoutineExerciseChooserViewController alloc] init] autorelease];
[self.navigationController pushViewController:routineExerciseChooserViewController animated:YES];
}
-(void)addExercise
{
if (managedObjectContext == nil)
{
managedObjectContext = [(CurlAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
[managedObjectContext retain];
}
UIBarButtonItem *addButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(exerciseChooser)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
NSError *error = nil;
Exercise *exercise = (Exercise *)[NSEntityDescription insertNewObjectForEntityForName:#"Exercise" inManagedObjectContext:managedObjectContext];
exercise.name = self.selectedExercise;
NSLog(#"addExercise theSelectedRoutine: %#", theSelectedRoutine);
[self.theSelectedRoutine addRoutineToExercisesObject:exercise];
if (![fetchedResultsController.managedObjectContext save:&error])
{
// Handle the error.
}
NSLog(#"%#", error);
NSLog(#"addExercise theSelectedRoutine: %#", theSelectedRoutine);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [routineTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Exercise *tempExercise = (Exercise *)[fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = tempExercise.name;
return cell;
}
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
NSLog(#"fetched results : \n%#\n",[self.fetchedResultsController fetchedObjects]);
// Commit the change.
NSError *error = nil;
// Update the array and table view.
if (![fetchedResultsController.managedObjectContext save:&error])
{
// Handle the error.
}
//[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [[managedObject valueForKey:#"name"] description];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
[routineTableView deselectRowAtIndexPath:indexPath animated:YES];
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Exercise" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSLog(#"fetchedResultsController theSelectedRoutine: %#",theSelectedRoutine);
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"ANY exerciseToRoutine == %#", theSelectedRoutine]];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
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.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
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. 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();
}
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
#pragma mark - Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.routineTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.routineTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.routineTableView 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.routineTableView;
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.routineTableView endUpdates];
}
#end
Update, this method is in another viewController. Could this be causing the problem?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSString *selectedRow = [[self.exerciseArray objectAtIndex:indexPath.row]objectForKey:#"exerciseName"];
NSLog(#"Row Selected: %#", selectedRow);
RoutineDayTableViewController *routineDayTableViewController=[self.navigationController.viewControllers objectAtIndex:([self.navigationController.viewControllers count] -3)];
routineDayTableViewController.selectedExercise = selectedRow;
[routineDayTableViewController addExercise];
[routineDayTableViewController release];
[self dismissView];
}
and here is another method in yet another viewController that access this class:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
RoutineDayTableViewController *detailViewController = [[RoutineDayTableViewController alloc] initWithNibName:#"RoutineDayTableViewController" bundle:nil];
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
detailViewController.title = [[managedObject valueForKey:#"name"] description];
detailViewController.theSelectedRoutine = [__fetchedResultsController objectAtIndexPath: indexPath];
NSLog(#"detailViewController.theSelectedRoutine:%#",detailViewController.theSelectedRoutine);
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
The error message means that you are sending a message to an object whose retain count went to zero and was subsequently deallocated. Based on your code snippet, I think your problem is this code -
RoutineDayTableViewController *routineDayTableViewController=[self.navigationController.viewControllers objectAtIndex:([self.navigationController.viewControllers count] -3)];
routineDayTableViewController.selectedExercise = selectedRow;
[routineDayTableViewController addExercise];
[routineDayTableViewController release];
The release call here looks suspicious. You are releasing something you haven't alloced here.
Have you run static analyzer on your code? It is helpful in detecting bugs of this kind.
The instance of the class RoutineDayTableViewController is the problem, so it's not likely in the code you posted here.
Check the code where you use this class.
It may help you to build using the Analyse option, this often detects the problem.
There are some place where the instance of RoutineDayTableViewController class is release and after that you are trying to access it.
If you want to check , Please don't release that object and try to run.

Is there a problem with my Core Data code

I have had a customer contact me about a bug in my application which I believe is related to my use of Core Data. This is currently only a theory as I have not be able to re-create the issue myself.
The problem appears to be if the user creates a new entity object and then edits the entity object straight away it does not seem to persist correctly and the next time the user opens the application the object is not there.
If the user creates the entity object and exits the app without editing it the problem does not occur, and if the user edits the object after re-opening the app it also does not occur.
I have only had one report of this happening and the device in question is a iPhone 3G running IOS4.0.2. I can not re-create it on an iPhone 3GS, iPhone 4 or via the simulator (all running 4.0.2)
I am no Core Data expert and am wondering if there is a problem with the way I am using the framework. I would really appreciate it if someone would review the code I have below and let me know if they see any potential problems.
I have included the methods that interact with core data from the relevant classes.
AppDelegate
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSError *error = nil;
if (managedObjectContext_ != nil) {
if ([managedObjectContext_ hasChanges] && ![managedObjectContext_ save:&error]) {
// log
}
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
NSError *error = nil;
if (managedObjectContext_ != nil) {
if ([managedObjectContext_ hasChanges] && ![managedObjectContext_ save:&error]) {
// log
}
}
}
- (NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext_ != nil) {
return managedObjectContext_;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext_;
}
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
managedObjectModel_ = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return managedObjectModel_;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"mydatabase.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"An error occurred adding the persistentStoreCoordinator. %#, %#", error, [error userInfo]);
// Display an alert message if an error occurs
}
return persistentStoreCoordinator_;
}
Controller 1 (creates the entity object using an image selected via the Image Picker)
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage* selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
// Create an image object for the new image.
NSManagedObject *image = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:managedObjectContext];
// Set the image for the image managed object.
[image setValue:[selectedImage scaleAndCropImageToScreenSize] forKey:#"image"];
// Create a thumbnail version of the image.
UIImage *imageThumbnail = [selectedImage thumbnailImage:50.0F];
// Create a new note
Note *note = [NSEntityDescription insertNewObjectForEntityForName:#"Note" inManagedObjectContext:managedObjectContext];
NSDate *now = [[NSDate alloc] init];
note.createdDate = now;
note.lastModifiedDate = now;
[now release];
note.thumbnail = imageThumbnail;
note.image = image;
}
[self dismissModalViewControllerAnimated:YES];
[picker release];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NotePadViewController *notePadController = [[NotePadViewController alloc] initWithNibName:#"NotePadView" bundle:nil];
Note *note = [[self fetchedResultsController] objectAtIndexPath:indexPath];
notePadController.note = note;
notePadController.title = note.notes;
[self.navigationController pushViewController:notePadController animated:YES];
[notePadController release];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Note" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"createdDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
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];
NSError *error = nil;
if (![fetchedResultsController performFetch:&error]) {
NSLog(#"An error occured retrieving fetched results. %#, %#", error, [error userInfo]);
// Display an alert message if an error occurs
}
return fetchedResultsController;
}
- (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 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 reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
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;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self setControllerTitle];
[self.tableView endUpdates];
}
Controller 2 - Used to view and edit the entity object (add text)
- (void)saveAction:(id)sender {
NSDate *now = [[NSDate alloc] init];
if (self.note != nil && textView.text.length == 0) {
self.note.notes = nil;
self.note.lastModifiedDate = now;
} else if (textView.text.length != 0) {
// Create a new note if one does not exist
if (self.note == nil) {
VisualNotesAppDelegate *appDelegate = (VisualNotesAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
Note *newNote = [NSEntityDescription insertNewObjectForEntityForName:#"Note" inManagedObjectContext:managedObjectContext];
self.note = newNote;
self.note.createdDate = now;
}
self.note.lastModifiedDate = now;
self.note.notes = textView.text;
self.title = [note title];
}
[now release];
[self.textView resignFirstResponder];
}
Many thanks in advance.
First, you really should not be ignoring those errors. You could be throwing an error there and never know it.
Nothing really jumps out about the code itself, but you could easily be having an error during the save, perhaps a non-optional parameter not being set, etc.
Handle the exceptions and see from there.
The problem is that I am only saving the managed object context when the app terminates or is backgrounded.
I only have about 5 seconds for all of the image blobs that were created by the user to be saved before the app exits. If there are a lot of images then they may not all save in time which is why they do not appear when the app is restarted.
This mainly effects the iPhone 3G as it is the slowest of all the models I support.
I have updated my code to save the managed context when ever a change is made. This has resolved this problem but has exposed another issue:
https://stackoverflow.com/questions/3578951/nsfetchedresultscontrollerdelegate-methods-not-being-called-when-camera-image-sav
If the entity is only created in the saveAction: method then it might not get created in the first place if the user quits the application before manually choosing save.