How to delete table rows programmatically? - iphone

I have a table which I am manipulating with a tableViewController (no nib, and the controller is creating the table behind the scenes)
I'm trying to delete a row from the table based on its row number; I can delete it from the array I use to create the cell in cellForRowAtIndexPath, but I get a strange error if I try to do the following, which is the same code as in tableView:commitEditingStyle:forRowAtIndexPath:
where it works fine
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i+1 inSection:1]
[self.tableView deleteRowsAtIndexPaths:[NSArray
arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
It gives an error
-[_WebSafeForwarder forwardInvocation:]
and then jumps out of the method but does not crash the app
Can anyone help?

You need to do that in this block.
[self.tableView beginUpdates];
///
[self.tableView endUpdates];
do notice one thing that when the the block reaches its end
- (void) numberOfRowsInSection:(NSInteger)section
will be called again. so u need to update the number of rows in table view also.
Hope this helps.
Thanks,
Madhup

Well you are missing a semi-colon on your first line.
When in doubt, clean up your syntax...
As a point of observation, most of the programmers I have worked with and talked to really hate UITableViewController. It really adds nothing to the functionality for the user and only obfuscates things that developers might really like to control... such as the position of the table via a XIB.
It's just a convenience class and in my experience, causes more issues than it prevents.

Related

cellForRowAtIndexPath Usage Pattern

In the below code I want to perform some DB actions when a cell is deleted, therefore I need to send my Server information about the cell being deleted. If I remember correctly cellForRowAtIndexPath should never be called directly, However I cannot think of any other way to get cell info in the below method, so my question is:
Is it acceptable to call cellForRowAtIndexPath manually below:
[tableView cellForRowAtIndexPath:indexPath]);
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[localGlobalNotifications removeObjectAtIndex:indexPath.row];
[notificationTableView beginUpdates];
[notificationTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[self postLeaveRequest];
NSLog(#"Row is : %#", [tableView cellForRowAtIndexPath:indexPath]);
[notificationTableView endUpdates];
}
}
To clarify: I understand that I can invoke a delegate call of cellForRowAtIndexPath by calling reloadData, what I'm trying to do is access the cell being deleted within commitEditingStyle. I'm not trying to reload my tableView, instead I want to get a reference to the cell being deleted. - Is it acceptable to obtain a reference to said cell by calling cellForRowAtIndexPath directly?
There's nothing wrong with asking the TableView to give you the cell, just as you do in your sample code.
Here's the documentation for the return value:
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
If you're annotating the cell with 'model' data then I think you're breaking the MVC pattern. Your view doesn't need to know about the model data in this way, and so querying the view to make a database change will make life difficult you in the future (readability, extensibility and reusability for example)
You would be better off having your DB metadata stored in a collection such as an NSArray - or an NSArray of NSArrays.
Then you could get all the data you need with something like:
id modelData = myModel[indexPath.section][indexPath.row];
Yes you can use cellForRowAtIndexPath: as they have also used in apple docs or you can create an array of your cells then you can delete it from there and reflect it in your database.
You can call cellForRowAtIndexPath: method using following line, here it will automatically calls all delegate and datasource methods.
[tableView reloadData];
or
[tableViewObjct reloadData];
Hope this solves your problem!

UITableViewDataSource methods not getting called

I am updating my UITableView with the following code:
int index = [self.guests indexOfObject:_guests];
[[self tableView] beginUpdates];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
[[self tableView] endUpdates];
But after calling [[self tableView] endUpdates], its not calling its datasource methods. In case of adding row, it calls its datasource methods. I think in case of deletion it does not need to ask anything to its datasource, but in case of adding a row it needs to ask its datasource about pretty much everything about the new row added like cellForRow, height etc etc. I just want to make sure that is it right if deleteRowsAtIndexPaths is not calling any of its datasource methods??
deleteRowsAtIndexPaths:indexPaths doesn't call delegate methods in all situation. It just simply deletes rows, If the rows being deleted are so many that they are occupying screen area, then it may call your delegate method. You have to explicitly call reloadData so that it refreshes its rows.
But calling reloadData immediately will spoil your animation or produce weird errors, since its rows are being deleted and you call reload method. (It may go crazy). The alternate solution is to call reloadData after a slight delay like 0.2 or greater like this:
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView performSelector:#selector(reloadData) withObject:nil afterDelay:0.2]; //calling reloadData after a short delay. 0.2 or whatever suits you.
Don't forget about deleting data from your datasource's array or whatever you are using to hold data.
The answer is a question " why should it call all the datasource and delegate methods if the view is already popuated and all cells are readily available"?
well it Should be the behavior

moveRowAtIndexPath doesn't refresh the cell's content

iOS 5 finally added a moveRowAtIndexPath:toIndexPath: method to UITableView. Problem is, calling it only moves the row, it doesn't update/redraws it. The only way to do so seems to be by finding the cell and calling setNeedsDisplay directly. But this is tricky since you never know whether the cell already got moved or not.
I can't call reloadRowsAtIndexPaths: either, since I'm not allowed to have 2 animations applied to a single row inside the same beginUpdates-endUpdates block.
There are may workarounds I can think of, like splitting the whole thing into 2 animation blocks. But I'm wondering whether I'm missing something, as the whole behavior seems odd to me.
Finally received an official answer from Apple regarding this issue:
Engineering has determined that this issue behaves as intended based
on the following information:
Only insert and update cause the cell to be requested from the data
source (the first because it's new and the second because you've
explicitly told the table view to update the cell). Delete and move
don't update the contents because that's not implicit in the action
being performed. If you need to change the contents of the cell
during the update, just reach into the cell and change the contents.
They like making it difficult for us don't they ;-)
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
// old API
//[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
//[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
// new API
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:newIndexPath]; // note use of newIndexPath
[tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; // NS_AVAILABLE_IOS(5_0);
break;
Note in the ChangeMove, configureCell has different params compared to its use in ChangeUpdate so be careful if copy pasting. Rather tricky to get that right.

iPhone/Obj-C - Archiving object causes lag

Hi I have a table view, and when I delete a cell I am removing an item from an NSMutableArray, archiving that array, and removing the cell.
However, when I do this, it is causing the delete button to lag after I click it. Is there any way to fix this?
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
int row = [indexPath row];
[savedGames removeObjectAtIndex:row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
//this line causing lag
[NSKeyedArchiver archiveRootObject:savedGames toFile:[self dataFilePath]];
}
}
Thanks
It sounds like you know the answer already -- don't archive everything on every delete. This can be done a number of ways -- archiving only pieces at a time, delayed archiving (periodically/when quitting/other policies), or making your custom archiving code considerably faster, which I doubt would even help that much in the scheme of things. I've heard that MAKeyedArchiver was faster than NSKeyedArchiver, but I believe this was some time ago, and designed for the mac+potentially platform specific (on the bright side it was intended as a drop in replacement for the NSKeyedArchiver API of the time, so it should be little integration time if you choose to use it).

How do you handle section inserts with an NSFetchedResultsController?

I've got an NSFetchedResultsController as my data source and and I implement NSFetchedResultsControllerDelegate in my custom UITableViewController. I'm using sectionNameKeyPath to break my result set into multiple sections.
In one of my methods, I'm adding a couple of objects to the context, all of which are in a new section. At the moment where I save the the objects, the delegate methods are called properly. The order of events:
// -controllerWillChangeContent: fires
[self.tableView beginUpdates]; // I do this
// -controller:didChangeSection:atIndex:forChangeType: fires for section insert
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
// -controller:didChangeObject:atIndexPath:forChangeType:newIndexPath fires many times
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITavleViewRowAnimationFade]; // for each cell
// -controllerDidChangeContent: fires after all of the inserts
[self.tableView endUpdates]; // <--- Where things go terribly wrong!!!
On the last call, "endUpdates", the application always crashes with:
Serious application error. Exception was caught during Core Data change processing:
[NSCFArray objectAtIndex:]: index (5) beyond bounds (1) with userInfo (null)
It seems that the table updates are not in sync with the NSFetchedResultsController data in some way, and things blow up. I'm following the docs on NSFetchedResultsControllerDelegate, but it's not working. What's the right way to do it?
UPDATE: I've created a test project that exhibits this bug. You can download it at: NSBoom.zip
Tracing through the app, I note didChangeSection is called first, which inserts a whole section - and then didChangeObject is called repeatedly.
The problem is that in didChangeSection you insert a whole section, then right after before the table view is updated you are adding objects to that same section. This is basically a case of overlapping updates... (not allowed even in a begin/end updates block).
If you comment out the individual object insert, it all works:
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
If you comment out the section insert:, it doesn't work - but I have had less luck with insertRowsInSections working all the time, and it may well be because there is no section yet (which I'm sure is why you were inserting the section to begin with). You may have to detect either case to do inserts with the right granularity.
In general I've had a lot more luck reloading and inserting whole sections than rows, the table view seems very fiddly to me around those working. You can also try UITableViewRowAnimationNone which seems to operate successfully more often.
I'm having the same problem. I find that didChangeSection fires off twice. Once when you create the object for insert, and once when you actually save it. To me, it shouldn't call didChangeSection until save is called. Or at the very least, willChangeSection would be called on creation of the object, and didChangeSection called when it is saved.
Now I'm looking into the NSManagedObjectContextDidSaveNotification observer method. This isn't part of the NSFetchedResultsControllerDelegate protocol, but you can register to receive it. Perhaps that will only be called when I actually call save and not before.