Steven Steven - 3 months ago 45
iOS Question

UITableView add new rows to bottom with animation without reloading entire table

I have a UITableView and I want to be able to add new rows to the bottom with animation, I was using

tableView reloadData
and that was working alright, but I want animation. So I switched over too
tableView reloadSections
but the problem is if I have say 7 rows visible it animates all 7 of those rows. I only want it to animate adding the new row, and not every row on the screen as if it was being added.

Any idea of how to do this?

Answer

Under UITableView you can call a method

- (void) insertRowsAtIndexPaths:(NSArray*) indexPaths
              withRowAnimation: (UITableViewRowAnimation) animation

This allows you to specify the rows in section were you would like to insert a new cell and pass what animation you would like to use. Example code:

//
//  InsertingNewRowToBottomOfTableViewController.m
//  Testing-Code-Snippets
//

#import "InsertingNewRowToBottomOfTableViewController.h"

@interface InsertingNewRowToBottomOfTableViewController ()
@end

#define kTestResuseCellIdentifier @"kTestResuseCell"

@implementation InsertingNewRowToBottomOfTableViewController
{
    NSMutableArray *testArray;
}

- (void) viewDidLoad
{
    [super viewDidLoad];
    self->testArray = [[NSMutableArray alloc] initWithArray:@[@"Test 1", @"Test 2", @"Test 3"]];

    UIRefreshControl *customRefreshControl = [[UIRefreshControl alloc] init];
    customRefreshControl.backgroundColor = [UIColor purpleColor];
    [customRefreshControl addTarget:self action:@selector(onRefresh:) forControlEvents:UIControlEventValueChanged];
    self.refreshControl = customRefreshControl;
}

- (void) didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

- (void) onRefresh: (UIRefreshControl*) refreshControl
{
    [refreshControl endRefreshing];
    [self->testArray addObject:[NSString stringWithFormat:@"Test %lu", self->testArray.count + 1]];
    [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self->testArray.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
    NSLog(@"Added a new cell to the bottom!");
}

#pragma mark - Table view data source

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

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


- (UITableViewCell*) tableView: (UITableView*) tableView cellForRowAtIndexPath: (NSIndexPath*) indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kTestResuseCellIdentifier forIndexPath:indexPath];
    cell.textLabel.text = [self->testArray objectAtIndex:indexPath.row];
    return cell;
}
@end

Update: Swift 3 Version

import UIKit

class InsertingNewRowToBottomOfTableViewController: UITableViewController
{
    private let testResuseCellIdentifier:String = "kTestResuseCell"
    private let testArray:NSMutableArray = NSMutableArray(array:["Test 1", "Test 2", "Test 3"])

    // MARK: - View Events

    override func viewDidLoad()
    {
        super.viewDidLoad()

        let refreshControl:UIRefreshControl = UIRefreshControl()
        refreshControl.backgroundColor = UIColor.purple
        refreshControl.addTarget(self, action:#selector(self.onRefresh(refreshControl:)), for:.valueChanged)
        self.refreshControl = refreshControl
    }

    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
    }

    // MARK: - UIRefreshControl

    @objc private func onRefresh(refreshControl: UIRefreshControl)
    {
        refreshControl.endRefreshing()
        testArray.add("Test \(self.testArray.count + 1)")

        let indexPath:IndexPath = IndexPath(row:(self.testArray.count - 1), section:0)
        self.tableView.insertRows(at:[indexPath], with: .left)
        NSLog("Added a new cell to the bottom!")
    }

    // MARK: - UITableViewDataSource

    override func numberOfSections(in tableView: UITableView) -> Int
    {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return self.testArray.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier:testResuseCellIdentifier)!
        cell.textLabel?.text = self.testArray.object(at: indexPath.row) as? String
        return cell
    }
}
Comments