UITableView deleteRowsAtIndex & datasource troubles - objective-c

Happy 14th Baktun everybody!
This thing has been bugging me for a while, and I've googled to and fro, and also checked SO for an answer, but I just can't seem to grasp the answers and apply it to my app. If some of you would be so kind to help out, that would be appreciated!
I got the following function that deletes a cell from an UITableView when a stepper reaches 0.
I want the cell to be animated to the left, so when you press the - sign you'll see the cell swipe out of view. I set the sender.tag to the [[cell stepper]setTag:indexPath.row]; in CellForRowAtIndexPath.
- (void)stepperChanged:(UIStepper *)sender
{
WinkelWagen *ww = [WinkelWagen sharedWinkelWagen];
BWBand *band = [ww.winkelWagenArray objectAtIndex: sender.tag];
NSDecimalNumber *nummer = [[NSDecimalNumber alloc]initWithFloat:sender.value];
band.winkelmandjeAantal = nummer;
[sender setValue:[band.winkelmandjeAantal doubleValue]];
// Remove the cell and object from WinkelWagen if stepper.value turns 0
if (sender.value == 0)
{
NSArray *deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:sender.tag inSection:0],
nil];
[ww.winkelWagenArray removeObject:band];
[self.tableView beginUpdates];
[self.tableView reloadData];
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationNone];
if ([winkelmandjeData count] == 0)
{
NSArray *addIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
nil];
[self.tableView insertRowsAtIndexPaths:addIndexPaths withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
}
UPDATE:
I've worked it out with [[self.tableview]reloaddata] but then the animation doesn't play. Can someone tell me how I should handle the data and the animation properly?
Thanks!

I see there something you could have a problem with, but don't know if it is an answer to your question. In your if ([shoppingCartData count] == 0) you are adding a row to the table, but the data source remains the same. That could result in a crash.
Also, before the if, your data source seem to be ww.shoppingCartArray, but in the if you are checking the count of shoppingCartData.
Hope this helps!

Related

iOS - deleting row from UITableView doesn't animate

I have a very frustrating issue. I have an app with a UITableView.
When I am removing a cell from the table view, it is removed from the data model and then I call the following:
-(void)removeItem:(NSIndexPath *)indexPath {
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
}
My problem is, I've tried it like I do above, and I've tried without using animateWithDuration. I've even tried with a CATransaction, but however I do it, the animation doesn't happen.
I've got slow animations on in my Simulator and when I remove an item from the list, it removes correctly, but without animation.
It just disappears and leaves a blank space for a moment before the table view data is reloaded.
I've search all over SO and Google and I can't seem to find an answer.
Any ideas?
Does it perhaps have to do with the fact that I'm removing the object from the data model before calling the function above?
Edit: Removed the Animation Block as it is incorrect
According to THIS you don't need to use the animateWithDuration:animations: at all.
Just try it like this
-(void)removeItem:(NSIndexPath *)indexPath {
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
}
Okay, I don't know if it's just bad manners, but I feel I'm going to answer my own question here.
First, thanks for all the other answers, you were all correct of course, but none of the answers solved my problem.
It turns out that there is another area in the code that does a different check then calls one of the tableView delegate methods, that seems to cancel the animation.
So the answer is as follows:
When you're row is removing but the animations aren't working, make sure that you are not calling didSelectRowAtIndexPath:indexPath before the animation starts. This will cancel the animations.
If you're NOT having that problem, here's some really typical code for expanding, two lines in the example:
Note that facebookRowsExpanded is a class variable you must have:
if ( [theCommand isEqualToString:#"fbexpander"] )
{
NSLog(#"expander button......");
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NSArray *deleteIndexPaths;
NSArray *insertIndexPaths;
facebookRowsExpanded = !facebookRowsExpanded;
// you must do that BEFORE, not AFTER the animation:
if ( !facebookRowsExpanded ) // ie, it was just true, is now false
{
deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
nil];
[tableView beginUpdates];
[tableView
deleteRowsAtIndexPaths:deleteIndexPaths
withRowAnimation: UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
else
{
insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
nil];
[tableView beginUpdates];
[tableView
insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation: UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
// DO NOT do this at the end: [_superTableView reloadData];
return;
}
NOTE: your code for numberOfRowsInSection must use facebookRowsExpanded
(it will be something like "if facebookRowsExpanded return 7, else return 5")
NOTE: your code for cellForRowAtIndexPath must use facebookRowsExpanded.
(it has to return the correct row, depending on whether or not you are expanded.)
First, you don't need to animate the change yourself.
Second, I think you need to make the changes to the datasource between the begin and end updates.
Your method should look something like this:
-(void)removeItemAtIndexPath:(NSIndexPath *)indexPath{
[self.tableView beginUpdates];
// I am assuming that you're just using a plain NSMutableArray to drive the data on the table.
// Delete the object from the datasource.
[self.dataArray removeObjectAtIndex:indexPath.row];
// Tell the table what has changed.
[self.tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
indexPaths is an array of NSIndexPaths to be inserted or to be deleted in your table.
NSarray *indexPaths = [[NSArray alloc] initWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
nil];

Cell is not being rendered in UITableView after insert/delete cells

I have a tableview which can work in 2 modes: read-only and read-write. In read-only mode table contains only 4 cells, in read-write mode - 8 cells. To transit between these 2 modes I have button with implemented action:
...
NSArray* cellIndexesInCurrentMode = [self cellIndexesInMode:Mode1];
NSArray* cellIndexInNewMode = [self cellIndexesInMode:Mode2];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths: cellIndexInNewMode withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:cellIndexesInCurrentMode withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
Everything works fine, until I scroll table down in read-write (8-cells), so it bounced up. After that, tapping on the button to transit to mode2 leads to situation when one of the cells are not renderred:
before transition:
after transition:
My investigation showed that for some reason this cell has alpha = 0 after the last layoutSubviews call on UITableView.
I am stuck, I have no idea what is going on, so any help will be much appreciated.
UPDATE:
Here is my cellForRowAtIndexPath method:
- (UITableViewCell*) tableView :(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)cellForRowAtIndexPath {
IDataSourceElement item = [[self collection] getDataSourceElementAtIndex:cellForRowAtIndexPath.row];
MyTableCell* cell = nil;
if ([item conformsToProtocol:#protocol(MyTableCellBuilder)]) {
cell = [(MyTableCellBuilder)item tableView:tableView buildCellForIndexPath:cellForRowAtIndexPath];
}
if (cell == nil) {
Class cellClass = [self getCellClassForElement: item];
NSString* reuseId = [cellClass description];
cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
if (!cell) {
cell = [[[cellClass alloc] initWithReuseIdentifier :reuseId] autorelease];
}
}
[self fillCell:cell forTableView:tableView forIndexPath:cellForRowAtIndexPath];
return cell;
}
And MyTableCellBuilder buildCellForIndexPath: method implementation
- (MyTableCell*) tableView :(UITableView*)tableView buildCellForIndexPath:(NSIndexPath*)buildCellForIndexPath {
MyTableCell* cell = myTableCell;
if ([cell isKindOfClass:[TextPropertyCell class]] == NO) {
cell = (MyTableCell*)[tableView dequeueReusableCellWithIdentifier :[self cellReuseIdentifier]];
if ([cell isKindOfClass:[MyTableCell class]] == NO) {
cell = [[[MyTableCell alloc] initWithReuseIdentifier :[self cellReuseIdentifier]] autorelease];
}
[self prepareCell:cell];
}
//cell setup
...
...
return cell;
}
My idea was that the problem with caching instance of UITableViewCell, I know that it is not best way to do, but anyway I had it. I tried to remove caching, so all I did I set myTableCell to nil, so it is never returned from this method. And... it helped! I don't know why and would really like to know the answer?
show your cellForRowAtIndexPath method implementation, i'm sure that's where you should look for mistake.

Add/Delete UITableViewCell with animation?

I know this might sound like a dumb question, but I have looked every where. How can I do this?
I know how to do this with a swype-to-delete method, but how cam I do it outside that function?
Please post some code samples.
Thanks!Coulton
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
insertIndexPaths is an array of NSIndexPaths to be inserted to your table.
deleteIndexPaths is a array of NSIndexPaths to be deleted from your table.
Example array format for index paths :
NSArray *insertIndexPaths = [[NSArray alloc] initWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
nil];
In Swift 2.1+
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([YourIndexPathYouWantToDeleteFrom], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([YourIndexPathYouWantToInsertTo], withRowAnimation: .Fade)
tableView.endUpdates()
And you can create an NSIndexPath easily like so:
NSIndexPath(forRow: 0, inSection: 0)
You want these two methods: insertRowsAtIndexPaths:withRowAnimation: and deleteSections:withRowAnimation: They are both detailed in the UITableView documentation.
Swift 4.0
tableView.beginUpdates()
tableView.deleteRows(at: [YourIndexPathYouWantToDeleteFrom], with: .fade)
tableView.insertRows(at: [YourIndexPathYouWantToInsertTo], with: .fade)
tableView.endUpdates()
For creating IndexPath use this:
IndexPath(row: 0, section: 0)

iOS NSMutableArray insertObject index out of bound

I'm new to Objective-C programming so sorry for the lame question.
I'm trying to write some simple app that would add single numbers stored in the NSMutableArray to the table view.
Here is the init code and code for addItem() method:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Test App";
self.navigationItem.leftBarButtonItem = self.editButtonItem;
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addItem)];
self.navigationItem.rightBarButtonItem = addButton;
self.itemArray = [[NSMutableArray alloc] initWithCapacity:100];
}
- (void)addItem {
[itemArray insertObject:#"1" atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
This line
[itemArray insertObject:#"1" atIndex:0];
produces following error:
*** -[NSMutableArray objectAtIndex:]: index 0 beyond bounds for empty array'
Why index 0 is beyond bounds for empty array and what I'm doing wrong???
UPD
As BP pointed out, insertion into array might not work for empty array, so I added following check:
if ([itemArray count] == 0) {
[itemArray addObject:#"1"];
} else {
[itemArray insertObject:#"1" atIndex:0];
}
So now I get same error message, but at line
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
Any ideas on what might be wrong with it?
The call you make in insertRowsAtIndexPaths:withRowAnimation: should be between a beginUpdates and endUpdates call on your tableView.
Check the TableView Programming Guide.
The initWithCapacity method only sets aside memory for the objects, it does not actually create the number of objects specified in your array.
I am not sure if you can insert into an array that has no objects in it yet, so you might want to try to use the addObject method if the array count is 0, and the insertObject method otherwise.

How do I prevent quickly tapping the Edit button for a UITableView multiple times from crashing the application?

I have a subclassed UITableViewController that performs some animations when editing mode is entered, namely removing the UIBarButtonSystemItemAdd from a navigation bar, and animating the first row of the table out of existence, and then back in when edit mode is exited:
- (void) setEditing: (BOOL) editing animated: (BOOL) animated
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow: 0 inSection: 0];
if (editing)
{
[self removeAddButton];
[datasource removeObjectAtIndex: 0];
[tableView deleteRowsAtIndexPaths: [NSArray arrayWithObject: indexPath] withRowAnimation: UITableViewRowAnimationTop];
}
else
{
[self restoreAddButton];
[datasource insertObject: #"First Row" atIndex: 0];
[tableView insertRowsAtIndexPaths: [NSArray arrayWithObject: indexPath] withRowAnimation: UITableViewRowAnimationTop];
}
[indexPath release];
[super setEditing: editing animated: animated];
}
Problem is, when the Edit button is tapped too quickly multiple times, and the animations haven’t finished yet, the application crashes with:
AppName(1824,0xa0ae5500) malloc: *** error for object 0x5f503a0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Obviously, something is going on where something being animated in or out isn’t having enough time to finish, so, how do I get around this? Am I doing something unnecessary/wrong?
Actually, this is a simple memory management error.
Remove this line:
[indexPath release];
The reason? This line:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow: 0 inSection: 0];
This returns a NSIndexPath with an effective retain count of 0. You don't need to release it.
For any kind of memory release error, you should run again with NSZombieEnabled turned on. It will give you a more complete error message.
You should also get in the habit of using Build & Analyze; it probably would have flagged this.