35

I went through all the examples online and could not figure out how to properly add a cell to a tableview with animation. Let's say I have one section with one cell and I want to add another cell once the user clicks on the first cell's accessory.

My "add" method does this:

- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {  
if (switch1.on) {

    NSArray *appleComputers = [NSArray arrayWithObjects:@"WWWWW" ,@"XXXX", @"YYYY", @"ZZZZ", nil];
    NSDictionary *appleComputersDict = [NSDictionary dictionaryWithObject:appleComputers forKey:@"Computers"];
    [listOfItems replaceObjectAtIndex:0 withObject:appleComputersDict];
    [tblSimpleTable reloadData];

}

Which is working but there is no animation. I understand that in order to add animation, I need to use insertRowsAtIndexPaths:withRowAnimation, so I tried tons of options but it always crashes when executing the insertRowsAtIndexPaths:withRowAnimation method.

My recent try was by doing this:

- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {  
if (switch1.on) {

    NSIndexPath *path1 = [NSIndexPath indexPathForRow:1 inSection:0]; //ALSO TRIED WITH indexPathRow:0
      NSArray *indexArray = [NSArray arrayWithObjects:path1,nil];   
     [tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];

}
}  

What am I doing wrong? How can I make this happen easily? I dont understand this whole indexPathForRow thing...I also dont understand how with this method I can add a label name to the new cell. Please help...thanks!!

TommyG
  • 4,145
  • 10
  • 42
  • 66

5 Answers5

24

It's a two step process:

First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.

Then insert your row:

[tblSimpleTable beginUpdates];
[tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
[tblSimpleTable endUpdates];

Simply inserting or deleting a row doesn't change your data source; you have to do that yourself.

Terry Wilcox
  • 9,010
  • 1
  • 34
  • 36
  • That's not enough: you should also put the data source changing code inside the beginUpdates/endUpdates section, because that's part of the "transaction" too. – ettore Nov 19 '12 at 06:17
  • You shouldn't put your datasource changing code inside the begin/end because it's independent of the table update. Separation of concerns. The "transaction" is just the table display update. – Terry Wilcox Nov 19 '12 at 17:09
  • 1
    indexArray is an array of new indexPaths (see the second section of the code in the question) – Terry Wilcox Dec 08 '14 at 15:46
22

The important thing to keep in mind when using insertRowsAtIndexPaths is that your UITableViewDataSource needs to match what the insert is telling it to do. If you add a row to the tableview, make sure the backing data is already updated to match.

Joshua Weinberg
  • 28,598
  • 2
  • 97
  • 90
  • what about my indices - are they correct assuming i am adding one more row to a one row table? – TommyG Aug 04 '11 at 14:56
  • ok, you were right - I deleted all the updates of my new table/dictionary and now it works. Thanks!! – TommyG Aug 04 '11 at 14:58
6

First of all, you should update your data model just before update table itself. Also you can use:

[tableView beginUpdates];
// do all row insertion/delete here
[tableView endUpdates];

And table will produce all changed at once with animation (if you specify it)

Serhii Mamontov
  • 4,942
  • 22
  • 26
  • I think you don't updated your data model before change. Can you post exception message? – Serhii Mamontov Aug 04 '11 at 14:54
  • 2011-08-04 10:54:55.601 ViewTest[36300:b903] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableView.m:995 2011-08-04 10:54:55.602 ViewTest[36300:b903] CoreAnimation: ignoring exception: Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted). – TommyG Aug 04 '11 at 14:55
  • crashes during [tableView endUpdates]; – TommyG Aug 04 '11 at 14:55
  • 1
    You see, debugger told you what number of rows in section 0 not respond to number of rows after update (depends on what are you doing insert/delete). Something wrong with dataSource, maybe it was changed during update block or wasn't changed to correspond new state at all. – Serhii Mamontov Aug 04 '11 at 15:01
3

The insertRowsAtIndexPaths:withRowAnimation: AND the changes to your data model both need to occur in-between beginUpdates and endUpates

I've created a simple example that should work on its own. I spent a week fiddling around trying to figure this out since I couldn't find any simple examples, so I hope this saves someone time and headache!

@interface MyTableViewController ()
@property (nonatomic, strong) NSMutableArray *expandableArray;
@property (nonatomic, strong) NSMutableArray *indexPaths;
@property (nonatomic, strong) UITableView *myTableView;
@end

@implementation MyTableViewController

- (void)viewDidLoad
{
    [self setupArray];
}

- (void)setupArray
{
    self.expandableArray = @[@"One", @"Two", @"Three", @"Four", @"Five"].mutableCopy;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.expandableArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //here you should create a cell that displays information from self.expandableArray, and return it
}

//call this method if your button/cell/whatever is tapped
- (void)didTapTriggerToChangeTableView
{
    if (/*some condition occurs that makes you want to expand the tableView*/) {
        [self expandArray]
    }else if (/*some other condition occurs that makes you want to retract the tableView*/){
        [self retractArray]
    }
}

//this example adds 1 item
- (void)expandArray
{
    //create an array of indexPaths
    self.indexPaths = [[NSMutableArray alloc] init];
    for (int i = theFirstIndexWhereYouWantToInsertYourAdditionalCells; i < theTotalNumberOfAdditionalCellsToInsert + theFirstIndexWhereYouWantToInsertYourAdditionalCells; i++) {
        [self.indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
    }

    //modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
    [self.myTableView beginUpdates];
    //HERE IS WHERE YOU NEED TO ALTER self.expandableArray to have the additional/new data values, eg:
    [self.expandableArray addObject:@"Six"];
    [self.myTableView insertRowsAtIndexPaths:self.indexPaths withRowAnimation:(UITableViewRowAnimationFade)];  //or a rowAnimation of your choice

    [self.myTableView endUpdates];
}

//this example removes all but the first 3 items
- (void)retractArray
{
    NSRange range;
    range.location = 3;
    range.length = self.expandableArray.count - 3;

    //modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
    [self.myTableView beginUpdates];
    [self.expandableArray removeObjectsInRange:range];
    [self.myTableView deleteRowsAtIndexPaths:self.indexPaths withRowAnimation:UITableViewRowAnimationFade];  //or a rowAnimation of your choice
    [self.myTableView endUpdates];
}

@end
jungledev
  • 4,195
  • 1
  • 37
  • 52
0

For the swift users

// have inserted new item into data source

// update
self.tableView.beginUpdates()
var ip = NSIndexPath(forRow:find(self.yourDataSource, theNewObject)!, inSection: 0)
self.tableView.insertRowsAtIndexPaths([ip], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
DogCoffee
  • 19,820
  • 10
  • 87
  • 120
  • Here is a tutorial in Swift ---> [UITableView Updates to Maintain the Integrity of the Original Trilogy](https://grokswift.com/uitableview-updates/), demo how to use insertRowsAtIndexPaths and beginUpdates – charles.cc.hsu Nov 02 '15 at 10:16