How to delete the NSManagedObject instance from Data Source? - iphone

I am trying to override tableView:commitEditingStyle:editingStyleforRowAtIndexPath: and having trouble implementing the deletion of the actual instance of a NSManagedObject that is represented in that row.
Apple says it should be done with the following code(Shown Here):
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object at the given index path.
NSManagedObject *eventToDelete = [eventsArray objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:eventToDelete];
// Update the array and table view.
[eventsArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
// Commit the change.
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
}
}
When I mimick this sample code in my app, every line works except for one line. The one line is: [bowlerArray removeObjectAtIndex:indexPath.row];. I get the error "Receiver type 'NSArray' for instance message does not declare a method with selector 'removeObjectAtIndex'".
What should that one line of code be?
Note: My line NSManagedObject *eventToDelete = [bowlerArray objectAtIndex:indexPath.row]; works just fine.
Update: Posting my actual code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [appDelegate managedObjectContext];
NSManagedObject *objectToDelete = [bowlerArray objectAtIndex:indexPath.row];
[moc deleteObject:objectToDelete];
[bowlerArray removeObjectAtIndex:indexPath.row];
[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
}
}

NSArray is immutable, so you can't modify it.
removeObjectAtIndex is not part of the NSArray API, because it would modify it.
You need an NSMutableArray to be able to do that.
If I do this :
NSMutableArray *arMu = [NSMutableArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", nil];
[arMu removeObjectAtIndex:0];
self.bigLabel.text = [arMu objectAtIndex:0];
the bigLabel is showing 1 for the index 0.
You error message is suggesting that you still have a NSArray instead of an NSMutableArray for the variable eventsArray
You can make a NSMutableArray from a NSArray like this :
NSMutableArray *arMu = [[NSMutableArray alloc] initWithArray:someNSArray];

Related

Sorted arrays doesn't delete what I want

I have a sorted NSMutableArray which works perfectly and all though when I try to delete an object it crashes the app then when I reload the app it didn't delete the right one.
I known that is due to the fact that this is now a sorted array because before I implemented this feature it worked fine though I haven't got a clue of how to fix it.
Here is the code I use to delete things from the array:
- (void) tableView:(UITableView *)tv commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if ( editingStyle == UITableViewCellEditingStyleDelete ) {
Patient *thisPatient = [patients objectAtIndex:indexPath.row];
[patients removeObjectAtIndex:indexPath.row];
if (patients.count == 0) {
[super setEditing:NO animated:YES];
[self.tableView setEditing:NO animated:YES];
}
[self deletePatientFromDatabase:thisPatient.patientid];
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
}
It is being stopped at this line:
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
Here is the code that I use for sorting the array:
-(void) processForTableView:(NSMutableArray *)items {
for (Patient *thisPatient in items) {
NSString *firstCharacter = [thisPatient.patientName substringToIndex:1];
if (![charIndex containsObject:firstCharacter]) {
[charIndex addObject:firstCharacter];
[charCount setObject:[NSNumber numberWithInt:1] forKey:firstCharacter];
} else {
[charCount setObject:[NSNumber numberWithInt:[[charCount objectForKey:firstCharacter] intValue] + 1] forKey:firstCharacter];
}
}
charIndex = (NSMutableArray *) [charIndex sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:#"cell"];
NSString *letter = [charIndex objectAtIndex:[indexPath section]];
NSPredicate *search = [NSPredicate predicateWithFormat:#"patientName beginswith[cd] %#", letter];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"patientName" ascending:YES];
NSArray *filteredArray = [[patients filteredArrayUsingPredicate:search] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
if ( nil == cell ) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
NSLog(#"indexPath.row = %d, patients.count = %d", indexPath.row, patients.count);
Patient *thisPatient = [filteredArray objectAtIndex:[indexPath row]];
cell.textLabel.text = [NSString stringWithFormat:#"%# %#", thisPatient.patientName, thisPatient.patientSurname];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.textColor = [UIColor blackColor];
if (self.editing) {
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
return cell;
}
I suspect this is quite a common thing that happens when you sort an array though it may not be.
Please say if you want any more code
It's not neccessarily because your array is sorted, but because the array you are populating your table from is not the same as the array that you are removing the object from.
Where are you sorting the patients array? Is the actual patients array being sorted or are you sorting it in your tableView delegates method and not actually sorting patients?
The reason for this is that the index of the object you deleted is not the same as the index that it has in the actual patients array (because one is sorted and one is not). Because of this, it is first deleting the wrong object, then it crashes because the tableView expects one to be deleted (so that it can animate that cell being removed) but the wrong one was deleted.

uiTableView crashes the second time in deleting a cell

I'n new in iPhone, I'm trying to delete a cell from my UITableView, the first time it deletes well, but the second time it gives me the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Invalid update: invalid number of rows in section 0. The number of rows
contained in an existing section after the update (3) must be equal to the number
of rows contained in that section before the update (3), plus or minus the number
of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or
minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
here is my code of table:
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
Book_own *temp= (Book_own *)[self.books objectAtIndex:indexPath.row];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[books removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
isDeleted = #"true";
deletedBook_id = temp.bo_id;
[self viewDidLoad];
}
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
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSDictionary *dictionary = [listOfItems objectAtIndex:section];
NSArray *array = [dictionary objectForKey:#"myBooks"];
return [array count];
}
in ViewDidLoad I wrote the following code:
NSDictionary *mbooks = [NSDictionary dictionaryWithObject:books forKey:#"myBooks"];
NSDictionary *mgroups = [NSDictionary dictionaryWithObject:filteredParts forKey:#"myBooks"];
listOfItems = [[NSMutableArray alloc] init];
[listOfItems addObject:mbooks];
[listOfItems addObject:mgroups];
Can anyone tell me how to solve this issue??
Thanks in advance.
What is happening is that you are either not deleting the item or you are deleting only one item when you allow multible deleting. You can adjust by checking if it is really deleted as below or reload data if not:
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
BOOL success = [self removeFile:indexPath];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
if (!success) [tableView reloadData];
}
}
This method then removes the datasource item (it is from my own project so the names should be adjusted:
-(BOOL) removeFile:(NSIndexPath *)indexPath{
// Removes from the datasource and the filesystem
NSURL *fileURL = [self.dataSource objectAtIndex:indexPath.row];
NSError *error;
BOOL success = [[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
if (success) {
[self.dataSource removeObjectAtIndex:indexPath.row];
} else {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:[error localizedDescription] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
[alert release];
[self.dataSource removeObjectAtIndex:indexPath.row];
}
return success;
}
If I'm reading your code correctly your data source is listOfItems. You should remove the row from your tables data source. A general rule is that when you remove or add items to a UITableView you must update the databsource.
[listOfItemsremoveObjectAtIndex:indexPath.row];
First, do not call [self viewDidLoad]; This method should not be called manually.
I think you are not calling the update method to your table view. This might fix your problems:
[tableView beginUpdates];
[books removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
edit: you also need to look at your code more closely. You are deleting a record from your datasource array directly from the row of the indexPath, which could be problematic considering the fact that your tableView has two sections.
The error says that there should be 1 fewer row after the deletion. It fails because the datasource code is not consistent in how it reports about the model.
Look at how you answer numberOfRowsInSection. Correctly, I think, first picking out the array that the section index represents, then answering that array's count.
The same logic must apply on the delete. The array you delete from needs to be the array indicated by the indexPath.section. Consider this:
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// the array you manipulate here must be the same array you use to count
// number of rows, and the same you use to render rowAtIndexPath
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"myBooks"];
// in order to be edited, it must be mutable
// you can do that on the fly here, or set it up as mutable
// let's make it temporarily mutable here:
NSMutableArray *mutableCopy = [array mutableCopy];
if (editingStyle == UITableViewCellEditingStyleDelete) {
[mutableCopy removeObjectAtIndex:indexPath.row];
// if it's setup mutable, you won't need to replace the immutable copy in the dictionary.
// but we just made a copy, so we have to replace the original
[listOfItems replaceObjectAtIndex:indexPath.section withObject:[NSArray arrayWithArray:mutableCopy]];
// and so on

Stange animation when deleting UITableViewCell

I'm using this code to delete a UITableViewCell, but when I swipe to delete, it is showing the minus sign on the right first, then deleting.
I took a video to help illustrate my point: Video on YouTube
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
[self.tableView setEditing: !self.tableView.editing animated:YES];
if (self.tableView.editing)
[self.navigationItem.leftBarButtonItem setTitle:#"Done"];
else
[self.navigationItem.leftBarButtonItem setTitle:#"Edit"];
}
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
PFObject *routine= [self.routineArray objectAtIndex:indexPath.row];
[routine deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[self.routineArray removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// [MKInfoPanel showPanelInView:self.view type:MKInfoPanelTypeError title:#"Routine Deleted" subtitle:#"You have succesfully deleted the routine!" hideAfter:2];
} else {
NSLog(#"%#", error);
}
}];
}
}
Edit:
- (void)loadData
{
PFQuery *query = [PFQuery queryWithClassName:#"Routine"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.routineArray = [objects mutableCopy];
[self.tableView reloadData];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
-(void)addRoutine
{
PFObject *routine = [[PFObject alloc] initWithClassName:#"Routine"];
[routine setObject:self.entered forKey:#"name"];
[routine saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[self loadData];
} else {
// There was an error saving the routine.
}
}];
}
It looks like there are two issues. The first it looks like -deleteInBackgroundWithBlock: is taking a noticeable amount of time to execute it's block after the delete button is pressed. You can try deleting the dataSource object and tableView row before deleting the data from the core data store if you aren't using a NSFetchedResultsController
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
PFObject *routine = [self.routineArray objectAtIndex:indexPath.row];
[self.routineArray removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[routine deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
//[MKInfoPanel showPanelInView:self.view type:MKInfoPanelTypeError title:#"Routine Deleted" subtitle:#"You have succesfully deleted the routine!" hideAfter:2];
} else {
NSLog(#"%#", error);
}
}];
}
}
You can also use a different animation if you prefer something other than fading out the opacity of the row. If you are targeting iOS 5.0 only, you can use UITableViewRowAnimationAutomatic to have UIKit attempt to choose the best looking animation given the circumstances.
The other issue looks like editing mode is turned back on after delete is pressed. You shouldn't need to override -setEditing:animated: so try removing that method completely.
In your -viewDidLoad: you can do the following to get editing behavior for free:
self.navigationItem.leftBarButtonItem = self.editButtonItem;
See:
An Example of Deleting a Table-View Row
UIViewController Class Reference: -editButtonItem
It should also be noted that when you are checking the editing status, you should use the isEditing accessor.
To avoid calling -reloadData, you just add the single new object to your dataSource array, then add a tableView row, then save it to the core data store. This is simply the opposite of what you had to do when deleting a row from the table view. This will work if you need to append the routine object to the end of the tableView and there is no custom sort order. Otherwise, you must insert the routine object into self.routineArray at the desired index and then create the proper indexPath to insert the tableView row at the desired location within the tableView.
- (void)addRoutine
{
PFObject *routine = [[PFObject alloc] initWithClassName:#"Routine"];
[routine setObject:self.entered forKey:#"name"];
[self.routineArray addObject:routine];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:([self.routineArray count]-1) inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath withRowAnimation:UITableViewRowAnimationAutomatic
[routine saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[self loadData];
} else {
// There was an error saving the routine.
}
}];
}

Hiding a specific row of UITableViewCell

I am working on a todo list application with CoreData + UITableView, I would like to hide the row that the user mark as done.
My current solution is invoke deleteRowsAtIndexPaths when user mark the task done and deduct the deleted row from the function of numberOfRowsInSection.
-(void)markDone:(NSIndexPath *) _indexPath{
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:_indexPath] withRowAnimation:UITableViewRowAnimationFade];
deletedCount = deletedCount + 1;
[self.tableView endUpdates];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
if (deletedCount>0) {
return [sectionInfo numberOfObjects]-deletedCount;
}
return [sectionInfo numberOfObjects];
}
Although this method does work, but I do need some code hacking here and there. Is there a way to invoke NSFetchedResultsController didChangeObject for changing of status of particular field?
Thanks
I think there are many ways to solve this. I'd just add a field in your managed object which states if a row is hidden or not"
Deleting will set this field accordingly.
What you need now is an NSFetchRequest with the corresponding predicate to filter hidden rows.
I just created a simple template app with core data support, and I think it is very easy to achieve:
I added a hidden BOOL property to the one entitiy given, with default NO.
Then I added this code to didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[selectedObject setValue:[NSNumber numberWithBool:YES] forKey:#"hidden"];
}
In - (NSFetchedResultsController *)fetchedResultsController {... I added
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"hidden == NO"];
[fetchRequest setPredicate:predicate];
after
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
This was it to hide cells by clicking(just for this example) on them.

Coredata on iphone: I can delete data only if I restart the application

I've a simple applications that lets you create groups of people form persons in your AddressBook... So Groups and Persons are in a one-to-many relationships, since a Group can have multiple persons. That's not a many-to-many since I create my own model of Person.
Adding data works without problems.
Deleting data doesn't. If I create a new Person, I must restart the app to delete it or the to delete the Group that Person belongs. Otherwise I get a "EXC BAD ACCESS" in the console. With NSZombieEnabled in the enviroment I get -[CFString release]: message sent to deallocated instance 0x75140d0.
I start with the CoreData stuff automatically created by XCode, create the RootViewController (subclass of TableViewController), I pass it the context and put it in a NavigationController.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Creo il controller root e gli passo il context
RootViewController *rvc = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
rvc.context = [self managedObjectContext];
//Creo il navcon, gli associo il root e lo rendo visibile
navCon = [[UINavigationController alloc] initWithRootViewController:rvc];
[window addSubview:navCon.view];
[window makeKeyAndVisible];
[rvc release];
return YES;
}
The RootViewController shows the Groups, then clicking on a row lets you modify persons in that group, passing the "nuovogruppo" (the Group Model associated with that row)
- (void)showPersoneControllerWithGruppo:(Gruppo *)nuovogruppo {
PersoneController *pc = [[PersoneController alloc] initWithStyle:UITableViewStylePlain];
pc.gruppo = nuovogruppo;
pc.context = self.context;
pc.delegate = self;
//NSLog(#"%#",[gruppi objectAtIndex:indexPath.row]);
[self.navigationController pushViewController:pc animated:YES];
[pc release];
}
And this is how I delete the person (gruppo is the Group model these persons belong to, persone is an array filled with these persons on viewDidLoad, removeGPObject is an accessor method generated by XCode (Group to Persons relationship))
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
}
}
I hope someone can help me...
UPDATE
Since I was having errors about messages sent to already released instances I tried commenting out all the [... release] lines and finally find out what was causing the problem. The problem was in the creation method of the record and not in the deleting method. Here is the method I use to create it.
The line that was causing the roblem is [NomeCognome release]
I'd be very grateful if someone could explain me why this line crashes the app.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
if (property == kABPersonPhoneProperty) {
ABMultiValueRef phoneProperty = ABRecordCopyValue(person,property);
NSString *phone = (NSString *)ABMultiValueCopyValueAtIndex(phoneProperty,identifier);
NSString *firstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *surname = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *NomeCognome = [NSString stringWithFormat:#"%# %#", firstName, surname];
[firstName release];
[surname release];
Persona *persona = (Persona *)[NSEntityDescription insertNewObjectForEntityForName:#"Persona" inManagedObjectContext:context];
persona.ABRR = phone;
persona.NomeCognome = NomeCognome;
[phone release];
[NomeCognome release]; //This line makes the app crash!!! Why???
[gruppo addGPObject:persona];
NSError *error;
[context save:&error];
[self.delegate PersoneControllerDidSave:self];
[self loadContentAndReload:YES];
[self dismissModalViewControllerAnimated:YES];
}
Why that line crashes the app?
Use reloadData method at the end as the following.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
[tableView reloadData];
}
It may work now.