Animate UITableView Data Source changes

Interface Setup

The UITableView has a method called reloadData that essentially removes all of the rows on the UITableView, reconstructs the datasource and then inserts all of the new rows. The issue with releadData is that it happens instantly. If you have an array that your UITableView is using as its datasource, and you remove an object from it, you should really have that object removal be animated. The row should slide out on either the left or right side of the UITableView and everything beneath it slide up.

In this post, I will walk through the steps to achieve that. it's fairly straight forward and will be really easy to reproduce in various different situations that require animating different types of UITableViewCell adjustments.

To start, we need a fresh project. Create a new Single View iOS project called TableView Row Animations and prefix the classes with TRA

This leaves us with a new blank project. Open the story board and add a UITableView to the View, take care that the UITableView does not take up the entire UIView as we need room along the top for another View. The upper portion of the UIView we will add a UISegmentedControl. Give the first index in the UISegmentedControl a title of "Original Data" and give the second index the title "Revised Data". It should look like the following:

Now you need to wire up the UITableView to the TRAViewController. You do that by selecting the UITableView, holding down Control and dragging down to the View Controller icon beneath the View you are editing. Releasing the mouse button will present you with a pop-over box with two options. A delegate and a datasource option; select the datasource and then repeat the process and select the delegate. The following video demonstrates this.

Finally, you need to set up a Action from the UISegmentedControl to your code. Open the assistant editor and control-drag from the UISegmentedControl over to the TRAViewController.m implementation. Create a method called toggleData. Set up a Outlet for your UITableView by control-dragging the UITableView over to your interface, giving it a name of tableView

UITableView Setup

We now have our UI completed, there is nothing left to do here so we can swap over to our TRAViewController.m file and start coding.

The file should be a blank template like the following:

#import "TRAViewController.h"

@interface TRAViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end

@implementation TRAViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

@end

We will need to add just one property, a NSMutableArray.

#import "TRAViewController.h"

@interface TRAViewController ()
@property (strong, nonatomic) NSMutableArray *data;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end

@implementation TRAViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

@end

In order for our UITableView to properly work, we also need to tell our View Controller that we are going to conform to two protocols. The UITableViewDataSource and UITableViewDelegate protocols. You can do that by editing the @interface TRAViewController line.

@interface TRAViewController () <UITableViewDataSource, UITableViewDelegate>

As of right now, our app will not run. The UITableView we added will try to invoke two methods that are TRAViewController does not implement yet. We can add them next.

The first method we implement is the numberOfRowsInSection method. The UITableView invokes this method when it's data is loaded/reloaded in order to determine how many rows it needs to provide us with. In order to give it a proper number, we will return the number of items in our data property.

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

The next method we invoke is the cellForRowAtIndexPath method. In this method, we will instance a UITableViewCell and assign it a value from our data array based on the current row in the UITableView.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];

    if (!cell) 
        cell = [[UITableViewCell alloc]  initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];

    [cell.textLabel setText:[self.data objectAtIndex:indexPath.row]];

    return cell;
}

Datasource setup

We now have the UITableView all set up and working properly. Since we have done nothing with our data property, the UITableView will not do anything. So let's create our initial data; this will be done in our viewDidLoad method.

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.data = [[NSMutableArray alloc] init];
    [self.data addObject:@"Test Item 1"];
    [self.data addObject:@"Test Item 2"];
    [self.data addObject:@"Test Item 3"];
    [self.data addObject:@"Test Item 4"];
    [self.data addObject:@"Test Item 5"];
}

Animate datasource changes

Now we can work on the meat of the app, animating changes made to our data. We will implement our toggleData method. In this method, we check which segment is selected and we edit our data based on this.

- (IBAction)toggleData:(UISegmentedControl *)sender {
    NSMutableArray *indexPaths = [[NSMutableArray alloc] init];

    // Determine which option is selected.
    // 0 = Original Data; 1 = Revised Data
    if (sender.selectedSegmentIndex == 0) {

        // Tell the table view we are going to update it's data.
        [self.tableView beginUpdates];
        // If original data, we restore our modifed data.
        [self.data insertObject:@"Test Item 1" atIndex:0];
        [self.data insertObject:@"Test Item 3" atIndex:2];
        [self.data insertObject:@"Test Item 5" atIndex:4];

        // Build an array of index's that need to be inserted into the tableview.
        // We match these to the index's we are adding to the data array.
        [indexPaths addObject:[NSIndexPath indexPathForRow:0 inSection:0]];
        [indexPaths addObject:[NSIndexPath indexPathForRow:2 inSection:0]];
        [indexPaths addObject:[NSIndexPath indexPathForRow:4 inSection:0]];

        // Next we insert them into the tableview.
        // We slide these rows in from the left.
        [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];

        // We end our updates.
        [self.tableView endUpdates];
    } else if (sender.selectedSegmentIndex == 1) {
        // Tell the table view we are going to update it's data.
        [self.tableView beginUpdates];

        // If revised data, we delete some items from the datasource.
        // Do this in reverse.
        [self.data removeObjectAtIndex:4];
        [self.data removeObjectAtIndex:2];
        [self.data removeObjectAtIndex:0];

        // Build an array of index's that need to be inserted into the tableview.
        // We match these to the index's we are adding to the data array.
        [indexPaths addObject:[NSIndexPath indexPathForRow:4 inSection:0]];
        [indexPaths addObject:[NSIndexPath indexPathForRow:2 inSection:0]];
        [indexPaths addObject:[NSIndexPath indexPathForRow:0 inSection:0]];


        // Next we delete them into the tableview.
        // We slide these rows in from the right.
        [self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationRight];

        // We end our updates.
        [self.tableView endUpdates];
    }
}

You can run this code and watch as the rows are animated in and out of your UITableView very nicely. It should look like the following:

Last notes

You can extend on this by supporting multiple sections in your UITableView if you want. If you have your new data handy, instead of calling reloadData, you can instead perform a beginUpdate, clear your datasource array, invoke the deleteRowsAtIndexPaths: method and then call endUpdates. Once you have done that, you can re-call beginUpdate, add your new data to your datasource array, invoke the insertRowsAtIndexPaths: and then call endUpdates. This will animate the reloading of your data source. This can be used in place of the reloadData method call, which just happens instantly with no animations.