I have a UITableView that displays attributes of entities that are in an array created with an NSFetchRequest. My instructor tells me for my purposes, I do NOT need a fetchedResultsController--so I do NOT need to add NSFetchedResultsControllerDelegate methods to my UITableViewController class. His analogy was putting a Porsche engine in a VW Beetle.
My tableView has 2 sections (0 and 1). Everything works fine until I try to delete objects from section 1 (section 0 is static).
With this commitEditingStyle code, the app crashes with his error:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:],
/SourceCache/UIKit_Sim/UIKit-3347.40/UITableView.m:1623
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete && indexPath.section == 1) {
// Delete cell
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
// Delete LocationCategory from managedObjectContext
LocationCategory *categoryToDelete = [self.locationCategories objectAtIndex:indexPath.row];
[self.managedObjectContext deleteObject:categoryToDelete];
[self.managedObjectContext save:nil];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
When I restart the app, the locationCategory (my NSManagedObject) is no longer in the managedObjectContext. I know I've got a problem updating the tableView, but I don't know how to resolve it. I've dug around, tried a bunch of prospective solutions, but nothing resolves the crashing.
Here's how my array is created:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.title = #"Select a Category";
// Core Data
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
self.managedObjectContext = [appDelegate managedObjectContext];
// Fetch LocationCategories & dump them in an array
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"LocationCategory" inManagedObjectContext:self.managedObjectContext]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"categoryName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
[fetchRequest setSortDescriptors:sortDescriptors];
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
self.locationCategories = categories;
[self.tableView reloadData];
}
Update:
Here's what the code looked like at the end when it worked:
// This was previously an NSArray. It needs to be an NSMutableArray
#property (nonatomic, strong) NSMutableArray *locationCategories;
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete && indexPath.section == 1) {
// Delete LocationCategory from managedObjectContext
LocationCategory *categoryToDelete = [self.locationCategories objectAtIndex:indexPath.row];
[self.managedObjectContext deleteObject:categoryToDelete];
[self.locationCategories removeObjectAtIndex:indexPath.row];
[self.managedObjectContext save:nil];
// Delete cell
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
Presumably your tableView datasource methods use self.locationCaregories. If so, the problem is that you don't remove the relevant Category from that array (although you delete it from the managedObjectContext, it remains in the array).
Now, because self.locationCategories is an NSArray, it is immutable, so you can't just remove the relevant item. You could reperform the fetch immediately after you delete the object, but that seems a bit inefficient. I recommend instead that you make self.locationCategories an NSMutableArray. You will need to amend the property definition, and also the line in viewWillAppear where you first set it:
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
self.locationCategories = [categories mutableCopy];
Then in the commitEditingStyle method, you can remove the relevant object from the array:
LocationCategory *categoryToDelete = [self.locationCategories objectAtIndex:indexPath.row];
[self.locationCategories removeObjectAtIndex:indexPath.row];
And as thorb65 says, move the code to delete the rows to the end of the method - you should get the data right, before you update the table.
Related
My app is set up like this, I have two view controllers coming off of my RootViewController - a simple main selection page with two buttons. The top button sends me to ViewController 1 where I take a photo and insert data in 6 text fields about the photo. I then hit a save button which saves those entities to my ManagedObjectModel TargetData inside my ManagedObjectContext. The second button on the main page leads to a TableVIewController where I have called an NSFetchedResults sorta thing to update the TableView.
It works pretty well...at least until I run the TableViewController once. I can add as many photos with text data as I want until I show the TableViewController, at which point, upon leaving the page, the TableViewController will only show the objects it loaded the first time I opened the page, not any items added after that. I have done a little searching and have found that after loading the TableVIew for the first time, the fetchResults is only seeing however many entities it saw the first time it loaded. For example, if I started the app, added 5 photos with text, then ran the TableViewController, I would see 5 items correctly displayed. I could go back to the main page and then add another photo with text, but if I went back to the TableVIewController I would only see the 5 photos I added before opening the TableViewController.
In the class for the TableViewController I have set the and am using all of it's normal methods inside my TableViewController.m file.
Totally clueless. Help!
EDIT: Here is some of my code
From: ViewController.m that will save data
- (IBAction)saveTarget:(id)sender {
//Only save if there is an image other than the stock photo
if (self.image != [UIImage imageNamed:#"Photo-Video-Slr-camera-icon"]) {
//Grab the main ManagedObjectContext from the AppDelegate
sTCAppDelegate *appDelegate =[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context =[appDelegate managedObjectContext];
//Get the Target model from CoreData
[self setTarget:[NSEntityDescription insertNewObjectForEntityForName:#"TargetData" inManagedObjectContext:context]];
//Set the properties of the TargetData model
self.target.weaponData = self.weaponData.text;
self.target.bulletType = self.bulletType.text;
self.target.stanceType = self.stanceType.text;
self.target.distanceData = self.distanceData.text;
self.target.targetNotes = self.targetNotes.text;
self.target.sightType = self.sightType.text;
self.target.scoreData = [NSNumber numberWithInt:[self.scoreData.text intValue]];
//Set image to smaller size for storage
UIImage *image = [self resizeImage:self.image toWidth:50 andHeight:50];
//Save as PNG NSData for compression
self.target.targetImage = UIImagePNGRepresentation(image);
//Set a date property to use for organizing by most recently saved
self.target.timeStamp = [NSDate date];
//Save to context
NSError *error = nil;
if ( ![context save:&error] ){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//Jump back to main page
[self.navigationController popToRootViewControllerAnimated:YES];
//set images back to nil for next time AddTarget is opened
image = nil;
self.image = nil;
NSLog(#"Data Saved");
}
//Return alert if user has not entered a photo
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"We're Sorry!" message:#"You must enter at least a photo to save target data." delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles:nil];
[alert show];
}
}
From: TableViewController.m that shows data
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
NSLog(#"number of sections:%lu",(unsigned long)[[self.fetchedResultsController sections] count]);
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSLog(#"%lu", [sectionInfo numberOfObjects]);
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSLog(#"Ran Cell Configure");
[self configureCell:cell atIndexPath:indexPath];
// Configure the cell...
return cell;
}
-(void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath{
TargetData *target = [self.fetchedResultsController objectAtIndexPath:indexPath];
UIImage *image = [UIImage imageWithData:target.targetImage];
cell.imageView.image = image;
cell.textLabel.text = [NSString stringWithFormat:#"%#", target.scoreData];
cell.detailTextLabel.text = target.weaponData;
}
#pragma mark- FetchedResultsControllerDelegate Methods
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
sTCAppDelegate *appDelegate =[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context =[appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TargetData" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:0];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedRequestsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedRequestsController.delegate = self;
self.fetchedResultsController = aFetchedRequestsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
- (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];
}
- (IBAction)mainMenu:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
So yes, I am using the Methods in the TableViewController implementation.
EDIT 2
I usually run my app on my actual iPhone to test it because then I can use the camera in my device. To see what the console might say about my problem, I added some images to the simulator and ran it on the simulator. This time, after adding an photo with text, opening the TableViewController, then adding another photo, I got a huge crash error report after trying to open the TableViewController again.
Here is the terminating part of the error:
2014-01-13 13:09:38.759 Target Tracker[25124:70b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'CoreData: FATAL ERROR: The persistent cache of section information does not match the current configuration. You have illegally mutated the NSFetchedResultsController's fetch request, its predicate, or its sort descriptor without either disabling caching or using +deleteCacheWithName:'
Any clue what that means?
RESOLVED
The problem was that I was caching the FetchResults when I ran the TableViewController. When I added another entity to the model and tried to return a new fetchResult, it didn't match the cached version which returned a critical CoreData error. I didn't see it as an error because I was not originally running the app in a simulator, but instead on my actual device. Once I ran it in the simulator I was able to see this error.
In short- I needed to set my "cacheName" to nil when I initialized the NSFetchedResultsController
NSFetchedResultsController *aFetchedRequestsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
For more information see this post: NSFetchedResultsController crashing on performFetch: when using a cache
The problem was that I was caching the FetchResults when I ran the TableViewController. When I added another entity to the model and tried to return a new fetchResult, it didn't match the cached version which returned a critical CoreData error. I didn't see it as an error because I was not originally running the app in a simulator, but instead on my actual device. Once I ran it in the simulator I was able to see this error.
In short- I needed to set my "cacheName" to nil when I initialized the NSFetchedResultsController
NSFetchedResultsController *aFetchedRequestsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
For more information see this post: NSFetchedResultsController crashing on performFetch: when using a cache
are you sure that you are saving the context after adding objects to it?
After inserting objects, call
- (BOOL)save:(NSError **)error;
The NSFetchedResultsController will not update until it receive a save context notification. This is usually performed when you call the method:
[yourManagedObjectContext save:&error]
when the fetched results controller receive the update, some calls are performed to its delegate, see the docs for something called NSFetchedResultsControllerDelegate and implement all of it's methods to update your tableview. That was the right way, you can also implement only one method to see if this works:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// In the simplest, most efficient, case, reload the table view.
[self.tableView reloadData];
}
It sounds like you are not implementing the NSFetchedResultsController delegate methods. Perhaps show some of the code for your UITableViewController?
Update
Ok, your code looks good; really good.
So the obvious, easy possibilities are out. Next step is to put some break points (or logs) into the -saveTarget:, -fetchedResultsController and the delegate methods and make sure everything is firing.
With the code you provided you should be seeing updates. Which hints at something not being fired.
I can't figure this out, but I seem to have a null indexPath when I delete an object from the NSFetchedResultsController.
When I delete my object, I do this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the object
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
[self saveContext];
}
}
Setting up NSFetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setFetchBatchSize:20];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Route" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *nameSort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = #[nameSort];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Routes"];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error with NSFetchedResultsController: %#", [error description]);
abort();
}
return _fetchedResultsController;
}
This is where the failure occurs:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
// Data was inserted - insert the data into the table view
case NSFetchedResultsChangeInsert: {
[self.savedRoutesTableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
// Data was deleted - delete the data from the table view
case NSFetchedResultsChangeDelete: {
[self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case NSFetchedResultsChangeUpdate: {
SavedRoutesTableViewCell *cell = (SavedRoutesTableViewCell *)[self.savedRoutesTableView cellForRowAtIndexPath:indexPath];
[cell configureCellWithRoute:[controller objectAtIndexPath:newIndexPath]];
break;
}
case NSFetchedResultsChangeMove: {
[self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.savedRoutesTableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
default:
break;
}
}
In the didChangeObject method, both my indexPath and newIndexPath are nil. I can NSLog my object and I do see the entity. It crashes in the [self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; method with the exception:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0] with userInfo (null)
When I save this object, I save it like this:
self.route.name = routeName;
NSManagedObjectContext *tempContext = [self.route managedObjectContext];
[tempContext performBlock:^{
NSError *error = nil;
if (![tempContext save:&error]) {
NSLog(#"an error occurred: %#", [error localizedDescription]);
}
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"error in main context: %#", [error localizedDescription]);
}
}];
}];
I'm not really sure where else to debug this since the NSFetchedResultsController just isn't returning me the indexPath for the deleted object. Any thoughts? Thanks in advance.
Edit:
Well I found the culprit causing the error, but I'm not sure why it does. Basically I have a ViewController that receives either a Route entity from the main MOC if it's Editing the route, or it inserts a new one if you are creating a new route. So in that viewController, if I'm editing a route, because I am trying to use two MOCs, one temp, and one main for its parent so I can easily throw away stuff if the user decides to cancel and not create a new route, I needed to transfer over that route to the other context to make other code I have work. So that "transfer" looks like:
NSManagedObjectContext *moc = _route.managedObjectContext;
NSManagedObjectID *routeId = [_route objectID];
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.tempContext.parentContext = moc;
NSManagedObject *localRoute = [self.tempContext objectWithID:routeId];
self.route = localRoute;
With this code, my adding on locations to an existing route works now that the locations are in the same contexts, but somehow it messes up deleting an existing route from the main MOC. Not sure why and what the best solution is.
Had the same problem and solved it by ensuring that NSFetchedResultsController always uses the same NSManagedContext. For example if u fetch object from a database with a fetch controller and later you want to delete that object, make sure that the fetch controller uses the same managed context it was using during fetching.
NSFetchedResultsController is optimised for working with UITableView and UITableView is user interface component, and user interface should always be handled by main thread, so there is no need to create new NSManagedContext every time you go into fetch... So implementing this code should fix this problem:
#synthesize fetchedResultsController = __fetchedResultsController;
#synthesize managedObjectContext = __managedObjectContext;
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = ap.persistentStoreCoordinator;
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
ap is a pointer to AppDelegate:
ap = [[UIApplication sharedApplication] delegate];
What this code does is creates one instance of MOC and later reuses it. Warning: this is not threadsafe, and it doesn have to be (cause you should use it with main thread only), so if you missuse it it will not work...
Use that:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
//create __fetchedResultsController
// do some fetching
return __fetchedResultsController;
}
So when you want to populate table:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [__fetchedResultsController.fetchedObjects count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSManagedObject *managedObject = [__fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [managedObject valueForKey:#"smth."];
return cell;
}
If your dataset changes just:
__fetchedResultsController = nil;
[tableView reloadData]
And everything including NSFetchedResultsControllers delegat methods will work fine, no nil indexPaths and so on...
Hope I helped someone...
NSManagedObjectContext *moc = _route.managedObjectContext;
NSManagedObjectID *routeId = [_route objectID];
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.tempContext.parentContext = moc;
NSManagedObject *localRoute = [self.tempContext objectWithID:routeId];
self.route = localRoute;
Had to get the context associated with the route.
I have successfully stored address book data in Core Data but i m not able to retrieve it and display it in tableView .What am i missing ?
This is how i fetch data from core data.
-(void)fetchFromDatabase
{
AddressBookAppDelegate *appDelegate =[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"AddressBook" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSError *error;
self.arrayForTable = [context executeFetchRequest:request error:&error];
NSLog(#"fetched data = %#",[self.arrayForTable lastObject]); //this shows the data
[self.tableView reloadData];
And this is my table view configuration.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.arrayForTable count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
if ([self.arrayForTable count]>0)
{
NSLog(#" table view content = %#",[self.arrayForTable lastObject]);// this doesn't log
AddressBook *info = [self.arrayForTable objectAtIndex:indexPath.row];
cell.textLabel.text = info.firstName;
}
}
return cell;
}
As it turned out in the discussion, self.arrayForTable was replaced by an empty array in viewWillAppear after fetching the objects in fetchFromDatabase.
Another problem was that each run of the program created new objects in the database,
leading to duplicate objects. You can either
delete all objects before inserting the new objects, or
for each name, check if a matching object already exists in the database, and insert a new one only if necessary.
More advanced techniques are described in "Implementing Find-or-Create Efficiently" in the "Core Data Programming Guide".
I am writing a simple app which lets users add entries to a database. The data is displayed in a UITableView. What I can't figure out is how to delete just one record from the database using the swipe-to-delete functionality of a tableview. I know that the code goes in this method:
-(void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
}
But I don't know how to fetch the record of the database that populates the cell that has been swiped.
I have a method which deletes all cells when the user clicks on a button on the navigation bar:
-(void)deleteAll {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Parameters" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *managedObject in items) {
[context deleteObject:managedObject];
}
if (![context save:&error]) {
}
[self.tableView reloadData];
}
But I don't how to customize this code to delete one record at a time. Any help to get me started would be appreciated.
I have this method as well...I would think this would delete the record permanently but it doesn't...
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[self.arr removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
[self.tableView reloadData];
}
I have done the same swipe to delete functionality using MCSwipeTableCell
For deleting a particular row from table with animation do this:
//I am passing 0,0 so you gotta pass the row that was deleted.
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:0 inSection:0]
[self.yourTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
For deleting from core data do this:
YourCustomModel *modelObj;
NSManagedObjectContext *context= yourmanagedObjectContext;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"YourCustomModel" inManagedObjectContext:context]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"yourField == %#", passTheFieldValueOfTheRowDeleted];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if ([results count]>0)
{
modelObj = (Customers *)[results objectAtIndex:0];
}
[context deleteObject:modelObj];
if (![context save:&error])
{
NSLog(#"Sorry, couldn't delete values %#", [error localizedDescription]);
}
You are almost there.. Just add a predicate to the fetch request.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(somePropertyInParameters = %d)",value];
[fetchRequest setPredicate:predicate];`
Also, you can get the index path of the swiped cell (and the Parameter object) in this method
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath`
Using core data on a on an application that has tabbed views. The second tab loads the core data, no errors show up in Xcode or when I run the app but when I click on the second tab the app crashes with the error "An Instance of NSFetchedResultsController requires a non-nil fetch request and managedObjectContext.
I'm new to core data and really struggling with this error so would appreciate any help I can get. The implementation file has the following code
- (void)setupFetchedResultsController
{
// 1 - Decide what Entity you want
NSString *entityName = #"EatCategory"; // Put your entity name here
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 3 - Filter it if you want
//request.predicate = [NSPredicate predicateWithFormat:#"EatCategory.name = Blah"];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupFetchedResultsController];
if ([[self.fetchedResultsController fetchedObjects] count] == 0) {
NSLog(#"No Results were fetched so nothing will be given to the table view");
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Eat Category Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// configure the cell...
EatCategory *eatcategory = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = eatcategory.name;
return cell;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.tableView beginUpdates]; // Avoid NSInternalInconsistencyException
// Delete the role object that was swiped
EatCategory *eatCategoryToDelete = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Deleting (%#)", eatCategoryToDelete.name);
[self.managedObjectContext deleteObject:eatCategoryToDelete];
[self.managedObjectContext save:nil];
// Delete the (now empty) row on the table
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self performFetch];
[self.tableView endUpdates];
}
}
Are you sure you are setting the managed object context properly? It seems that the fetch request is ok but what about the context? If the context is not set properly this could lead to that error.
Do you inject the context from an external object? If yes, how is declared the managedObjectContext property?
For example:
// from an external object
YourController *controller = ... // alloc-init the controller
controller.managedObjectContext = self.managedObjectContext;
// within your controller .h
#property (strong, nonatomic) NSManagedObject* managedObjectContext; // or retain if you don't use ARC
// within your controller .m
#synthesize managedObjectContext;
You could also grab the main context inside your controller from the application delegate (if you have declared it there) like the following:
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext* managedObjectContext = delegate.managedObjectContext;
but this could lead to a more rigid application design.
Some notes
Instead of using commitEditingStyle you could "register" for NSFetchedResultsControllerDelegate callbacks. This class has been created to deal with changes in table views. You could use it and respond in different manner for table changes.
Here the class reference for NSFetchedResultsControllerDelegate.
Hope it helps.