So basically I have a table view of folder objects and I want to be able to remove/ delete folders. So far if I try to delete, the folders are removed, but when I re run the application they are all back (so the delete is not saved). Any advice?
here is my delete method for the UITableView:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[self.folders removeObjectAtIndex:indexPath.row];
NSMutableArray *newSavedFolders = [[NSMutableArray alloc] init];
for (Folder *folder in self.folders){
[newSavedFolders addObject:[self folderWithName:folder.name]];
}
[tableView deleteRowsAtIndexPaths:#[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
}
}
and the folderWithName method is from here:
- (Folder *)folderWithName:(NSString *)name {
id delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
Folder *folder = [NSEntityDescription insertNewObjectForEntityForName:#"Folder" inManagedObjectContext:context];
folder.name = name;
folder.date = [NSDate date];
NSError *error;
if (![context save:&error]) {
//we have an error
}
return folder;
}
The reason it deletes is because of this line:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
That removes the row from view, but doesn't remove the data.
If your using CoreData then you simply do the following with a NSFetchedResultsController:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
[self.tableView reloadData];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Delete! %# %#", error, [error localizedDescription]);
return;
}
} 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
}
}
Add call your items with the NSFetchedResultsController this way:
#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:#"Folder" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number for displaying in UITableView.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
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;
}
Related
I am working on my first app and am in need of some assistance. I've read through tons of similar questions on SO but just not getting anywhere.
I have a simple table view controller which has a plus button; when pressed, that leads to a modal view controller asking the user to insert information into 4 separate fields. When the user clicks save, the modal view dismisses and the information is displayed in the table view because the save button calls the NSManagedObject subclasses and through Core Data, it saves it.
I'm trying to have it so that when a user types into the first field (name), if they have already typed that name before (if they added it to Core Data with the save method), it auto-populates and shows a hidden table view with entries matching that name. I first started working with a NSMutableArray but thanks to Jeff's comments, that would not persistently keep the data, so because I already have the Core Data functionality, it makes more sense to use that. I am editing this post to include how my Core Data is currently set up.
I basically want to achieve this but with Core Data (http://www.dalmob.org/2011/03/01/alternative-autocomplete-uitextfield/)
There is a Information Entity with a relationship to the People Entity.
- (IBAction)save:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Information *information = [NSEntityDescription insertNewObjectForEntityForName:#"Information" inManagedObjectContext:context];
People *enteredPerson = (People *)[People personWithName:self.nameTextField.text inManagedObjectContext:context];
information.whichPerson = enteredPerson;
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Can't save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
The enteredPerson calls the personWithName method in the People NSManagedObjectSubclass:
+ (People *)personWithName:(NSString *)name inManagedObjectContext:(NSManagedObjectContext *)context
{
People *people = nil;
// Creating a fetch request to check whether the name of the person already exists
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"People"];
request.predicate = [NSPredicate predicateWithFormat:#"name = %#", name];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *fetchedPeople = [context executeFetchRequest:request error:&error];
if (!fetchedPeople)
{
// Handle Error
}
else if (![fetchedPeople count])
{
// If the person count is 0 then let's create it
people = [NSEntityDescription insertNewObjectForEntityForName:#"People" inManagedObjectContext:context];
people.name = name;
}
else
{
// If the object exists, just return the last object .
people = [fetchedPeople lastObject];
}
return people;
}
Based on the suggestion to create the NSFetchRequest, I am wondering the best technique to do this.
Do I do this in the Save method of the Add Entry at the end to something like this:
// NSFetchRequest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Specifiy a predicate here if there are certain conditions your fetch must adhere to
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY name CONTAINS[c] %#", self.nameTextField.text];
[fetchRequest setPredicate:predicate];
//NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle error
}
if ([fetchedObjects count] == 0)
{
// Add entry to results
}
What I want to achieve is, from Core Data, when the user types in the name, reference core data (with a fetch request) and if that name exists, as the user starts typing, populate the Table view that sits below the Text field.
Any guidance would be appreciated.
EDIT: I have updated an answer with some further code to almost get this working.
EDIT: More Code:
Property Declarations in .h
#property (retain, nonatomic) IBOutlet UITextField *nameTextField;
#property (nonatomic, retain) NSString *substring;
#property (weak, nonatomic) IBOutlet UITableView *testTableView;
#property (nonatomic, retain) NSFetchedResultsController* autocompleteFetchedResultsController;
- (void)searchAutocompleteEntriesWithSubstring:(NSString *)substring;
ViewDidLoad
- (void)viewDidLoad
{
NSError *error;
if (![[self autocompleteFetchedResultsController] performFetch:&error])
{
NSLog(#"Unresolved error %# %#", error, [error userInfo]);
exit(-1);
}
self.testTableView.delegate = self;
self.testTableView.dataSource = self;
self.testTableView.hidden = YES;
self.testTableView.scrollEnabled = YES;
self.nameTextField.delegate = self;
[super viewDidLoad];
}
Save Method
- (IBAction)save:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Transaction *transaction = [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
People *enteredPerson = (People *)[People personWithName:self.nameTextField.text inManagedObjectContext:context];
transaction.whoFrom = enteredPerson;
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Can't save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Thanks,
I guess self.autocompleteUrls is the NSMutableArray u had previously... Ok, U have come a long way, now see the autocompleteFetchedResultsController -> that is what fetches, and the condition if (_autocompleteFetchedResultsController != nil) protects property method from being called every time U reference autocompleteFetchedResultsController. So U should do something like this:
- (void)searchAutocompleteEntriesWithSubstring:(NSString *)substring {
_autocompleteFetchedResultsController = nil;
[self autocompleteFetchedResultsController];
[self.testTableView reloadData];
}
and If U done everything else correctly that should be it...
Your cellFoRowAtIndexPath should look like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"autocomplete cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:CellIdentifier];
}
People *people = [self.autocompleteFetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = people.name;
return cell;
}
Using the basic code from the Xcode library of snippets you can perform a Core Data fetch:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"<#Entity name#>" inManagedObjectContext:<#context#>];
[fetchRequest setEntity:entity];
// Specifiy a predicate here if there are certain conditions your fetch must adhere to
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"<#Predicate string#>", <#Predicate arguments#>];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [<#context#> executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle error
}
Replace the Entity name with the one that stores your NameTextField entries. And fetchedObjects is an array that will store your information you need to populate your table with.
Obviously, you will also need to save any new NameTextField entries to core data by creating a new entity and saving the context.
I have sort of got this working. Rather than update the entire question, I have left that there for reference because I am sure someone will come across a similar situation. Through the use of a FetchedResultsController object within my view controller, I'm now getting a list of names to populate the table view that sits below the text field.
Let's look at some code:
- (NSFetchedResultsController *)autocompleteFetchedResultsController
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
if (_autocompleteFetchedResultsController != nil)
{
return _autocompleteFetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"People" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
if ([self.substring length] > 0) {
NSPredicate *peoplePredicate = [NSPredicate predicateWithFormat:#"ANY name CONTAINS[c] %#", self.nameTextField.text];
[fetchRequest setPredicate:personPredicate];
}
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.autocompleteFetchedResultsController = theFetchedResultsController; _autocompleteFetchedResultsController.delegate = self;
return _autocompleteFetchedResultsController;
}
- (void)viewDidLoad
{
NSError *error;
// I am performing the fetchHere and if there is an error, it will get logged.
if (![[self autocompleteFetchedResultsController] performFetch:&error])
{
NSLog(#"Unresolved error %# %#", error, [error userInfo]);
exit(-1);
}
// Further code relating to tableview to make it hidden, etc
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id sectionInfo = [[_autocompleteFetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
self.testTableView.hidden = NO;
self.substring = self.nameTextField.text];
self.substring = [self.substring stringByReplacingCharactersInRange:range withString:self.substring];
[self searchAutocompleteEntriesWithSubstring:self.substring];
return YES;
}
#pragma mark UITableViewDataSource methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"autocomplete cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
People *people = [self.autocompleteFetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = people.name;
return cell;
}
#pragma mark UITableViewDelegate methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
self.nameTextField.text = selectedCell.textLabel.text;
}
So this works to some extent. When I place the cursor in the nameTextField, it unhides the table view, but it currently shows me the name of ALL the names already entered.
What I want is the ability to, as I'm typing, for the table to only show me what matches that.
The [self searchAutocompleteEntriesWithSubstring:substring]; in the shouldChangeCharactersInRangeMethod is calling a custom method I created.
When I had this set to a NSMutableArray instead of using Core Data, it was the code below, but I have no idea how to adjust this code to say, search core data and only display the results that match what I am already typing.
- (void)searchAutocompleteEntriesWithSubstring:(NSString *)substring {
self.autocompleteFetchedResultsController = nil;
[self autocompleteFetchedResultsController];
[self.testTableView reloadData];
}
I'm almost there - just need a bit of a push to get there!
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 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`
i have a problem in my iOS App, i receive this error:
“NSObjectInaccessibleException - CoreData could not fulfill a fault”
When i update the database, i do it from another thread and i have in that thread this method:
#interface UpdateDatabase : NSOperation
#property (nonatomic,copy) NSString *name;
#end
#implementation UpdateDatabase
- (void)mergeChanges:(NSNotification *)notification
{
AppDelegate *appController = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appController managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
- (void)main {
AppDelegate *appController = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [[NSManagedObjectContext alloc] init];
[self.managedObjectContext setUndoManager:nil];
[self.managedObjectContext setPersistentStoreCoordinator: [appController persistentStoreCoordinator]];
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
[self checkForUpdate:self.name];
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Serial" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sectionNumber" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects: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:#"sectionNumber" 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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
- (void)checkForUpdate:(NSString *)name
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Serial" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//Controllo prima che la serie di cui voglio fare l'aggiornamento non sia in download, e non stia facendo già un aggiornamneto (caso mai per qualche strana ragione non ne siano partiti 2), prendo solo le altre.
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"serialName",self.name]];
NSError *error;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
//i update the object
}
if (![self.managedObjectContext save:&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();
}
}
#end
and i launch a new nsoperation for every element in the core data database, then in the view where i have the UITableView, i have this:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Serial" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sectionNumber" ascending:NO];
NSSortDescriptor *numberDayDescriptor = [[NSSortDescriptor alloc] initWithKey:#"numberOfDays" ascending:YES];
NSSortDescriptor *serialNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"serialName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor,numberDayDescriptor,serialNameDescriptor, 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:#"sectionNumber" 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)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];
}
Sometime the app crashes and some time don't crash, how i can handle this error? i have read some question about this problem on SO, and someone talk about remove the fetchbatchsize and the cache, what you think? where i wrong?
EDIT:
I have edited the code.
I have a UITableView within a UIViewController so that I can have a UILabel below the table. In doing so, I have had difficulties in adding an Edit/Done button. I couldn't do the traditional way, so I had to do a work around using the following idea:
1)Create the edit button up the top left on viewdidload:
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editbutton)];
2)Create code so that upon clicking the edit button, the tableview becomes editable, and it changes the title to done. Then upon clicking done, it goes back to saying edit.
-(IBAction)editbutton{
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(donebutton)];
[tableView setEditing:YES animated:YES];
}
-(IBAction)donebutton{
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editbutton)];
[tableView setEditing:NO animated:YES];
}
This part is all OK. I just have put it in for completeness. The tableview becomes editable when click the edit button, and i can click done and it goes back to normal. My problem is clicking the delete button (after clicking the Red minus button next to a row) doesn't delete the row. I have tried the following code:
NOTE:
1)context has been declared in the .h file as:
#property (nonatomic, retain) NSManagedObjectContext *context;
and synthesized in the .m file.
2)I have declared the #property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController in the .h file and then the #synthesize fetchedResultsController = _fetchedResultsController in the .m file
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object for the given index path
[context deleteObject:[_fetchedResultsController objectAtIndexPath:indexPath]];
// Save the context.
NSError *error = nil;
if (![context save:&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();
}
}
}
EDIT:
Ok, I have found a bit of a solution. I used the following code to delete my core data:
- (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]];
// Save the context.
NSError *error = nil;
if (![context save:&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();
}
[self fetchedresults];
[self.tableView reloadData];
}
}
My new problem is that when i click delete. The row is deleted but still remains there with the red minus button on a blank row. I can still click on the row too (which normally edits the data) but there is no data to load.
EDIT 2:
I forgot to add, to get it to work, i added this:
- (NSFetchedResultsController *)fetchedResultsController{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Record" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"activity" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects: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:context sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
EDIT 3:
This is what fetched results does:
- (void)fetchedresults {
NSManagedObjectContext *moc = [self context];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Record" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"activity" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil)
{
// Deal with error...
}
float tot = [[array valueForKeyPath:#"#sum.cpdhours"] floatValue];
totalhours.text = [NSString stringWithFormat:#"%.1f", tot];
}
I think that the managedObjectContext property you have is actually a parent context to your fetchedResultsController context, and thus when you delete an entity using it, the fetchedResultsController won't know that it's supposed to refetch and update the tableview. Try calling [self.fetchedResultsController.managedObjectContext deleteItem:yourItem].
Maybe it's not exactly like that I'm writing this on my iPhone but you get the idea. Also have you made sure to implement the fetched results controller delegate methods to update your tableview?
Your probably will need to recache your NSResultFetchedController. In your NSFetchedResultController init function, your are caching your fetch result into a cache named "Master". This probably explain the behavior you're experiencing.
You can either not used any cache by setting the cache name to nil when setting up the NSFetchedResultController
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
or you delete the cache right after you deleted your NSManagedObject.
[NSFetchedResultController deleteCacheWithName:#"Master"];
Are you using an NSFetchedResultsController? If not, try it and you shouldn't worry about reloading the table at all.
Also, there's absolutely no need to use a custom button. Just stick with the .editButtonItemand override -setEditing:animated:.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
}
Swift: In my case I want to completely wipe my tableView after the user signs out:
func clearData(){
NSFetchedResultsController.deleteCacheWithName("MyCache")
let indices = tableView.allIndices
let moc = NSManagedObjectContext.MR_defaultContext()
let fetchRequest = NSFetchRequest(entityName: "MyEntity")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
saveMoc()
do {
try moc.persistentStoreCoordinator!.executeRequest(deleteRequest, withContext: moc)
try fetchedResultsController.performFetch()
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths(indices, withRowAnimation: .Automatic)
tableView.endUpdates()
} catch let e as NSError {
print(e)
}
}
I made an extension to get all the NSIndexPaths for my tableview:
extension UITableView{
var allIndices: [NSIndexPath] {
var indices = [NSIndexPath]()
let sections = self.numberOfSections
if sections > 0{
for s in 0...sections - 1 {
let rows = self.numberOfRowsInSection(s)
if rows > 0{
for r in 0...rows - 1{
let index = NSIndexPath(forRow: r, inSection: s)
indices.append(index)
}
}
}
}
return indices
}
}