Show Menu

Expanding your company? Need a developer?

Submit your 30 day Job Listing for FREE

A common use case in iOS is creating a form or an editable list of records directly inside a UITableView. We could send the user to a separate “detail” view controller for editing, but that complicates and slows down the user experience.

In this post, I’ll show you how to create your own subclass of UITableViewController that can support any number of UITextFields inside your table cells and use them to update any backing data source. This code can then be reused in any project that needs an editable table view!

iOS Table View

Download the files:

Editing text fields directly in a UITableView is challenging for a few reasons:

  • A table can have many cells and each cell can have many text fields. We need to map changes in a specific text field back to our data source
  • UITableView is optimized to reuse UITableViewCell objects. As soon as you make a change to a UITextField‘s contents, it will be lost when you scroll off the screen unless saved immediately.

First create a new Objective-C class in Xcode called EditableTableViewController and make it a subclass of UITableViewController. Next, in the .h file, make this class implement UITextFieldDelegate, which is the delegate protocol for communicating with UITextFields. All of its methods are optional; we’ll only be using two.

First, when a user taps inside a UITextField, we call a method on the base controller’s UITableView that scrolls the selected cell to the middle of the screen

- (void)textFieldDidBeginEditing:(UITextField*)textField
  UITableViewCell* cell = [self parentCellFor:textField];
  if (cell)
    NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];

Next, when a user ‘leaves’ a UITextField, we send the indexPath and UITextField pointer to a public method we will create shortly. The subclass that will receive that message is responsible for updating its data source accordingly. Finally, we tell the UITableView to reload the cell so that it renders just as the table developer intended.

- (void)textFieldDidEndEditing:(UITextField*)textField
  UITableViewCell* cell = [self parentCellFor:textField];
  if (cell)
    NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
    [self textFieldDidEndEditing:textField inRowAtIndexPath:indexPath];
    [self.tableView reloadRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationNone];

In both methods we call a private method on self called parentCellFor: that returns the parent cell for our UITextField. Couldn’t we simply get the text field’s parent cell by calling this?


After all, doesn’t the view hierarchy for a UIView in a custom UITableViewCell looks like this?

UITableViewCell > contentView > UIView

Yes, but what if the text field happens to be nested inside another UIView?

UITableViewCell > contentView > UIView > textField

We need a more flexible solution that can handle any number of nested UIViews. Let’s try using recursion:

- (UITableViewCell*)parentCellFor:(UIView*)view
  if (!view)
    return nil;
  if ([view isMemberOfClass:[UITableViewCell class]])
    return (UITableViewCell*)view;
  return [self parentCellFor:view.superview];

As with any recursive call, we need a base case that terminates the call chain, and that is when our UIView input parameter is nil. Next, we perform the meat of our method and actually check if the input UIView is a UITableViewCell. If so, our work is done! Return the view after casting it as a UITableViewCell object. If not, we keep digging by calling parentCellFor: on our UIView’s superview. The process continues until we either return nil or our parent UITableViewCell!

Now, in the .m file, add the following instance method stub:

- (void)textFieldDidEndEditing:(UITextField*)textField inRowAtIndexPath:(NSIndexPath*)indexPath;

This is the method that subclasses will override in order to capture the event. This form of event handling is known as the Template Method Pattern or simply “subclass and override.” It is used everywhere in OOP, and you use it all the time when you override init or viewDidLoad, for example.

Sample Usage

Now let’s use our new code! In our example, we’ll be creating a table view with cells that contain 2 text fields.

First create a simple class to serve as our data model. In Xcode, create a new class called Person that inherits from NSObject and has two string properties:

@interface Person : NSObject

@property (strong, nonatomic) NSString* firstName;
@property (strong, nonatomic) NSString* lastName;


Now create a new class called TableViewController that inherits from our new EditableViewController class:

@interface TableViewController : EditableTableViewController

Create an NSArray to serve as our underlying data source. Make this a ‘private’ property by putting it into an anonymous category in the .m file:

@interface TableViewController ()

@property (strong, nonatomic) NSArray* people;


Initialize the array with some test data by overriding the designated initializer, initWithStyle. Use the new literal syntax for brevity. Bonus points if you recognize the names!

- (id)initWithStyle:(UITableViewStyle)style
  self = [super initWithStyle:style];
  if (self)
    Person* person1 = [[Person alloc] init];
    person1.firstName = "Erich";
    person1.lastName = "Gamma";

    Person* person2 = [[Person alloc] init];
    person2.firstName = "Richard";
    person2.lastName = "Helm";

    Person* person3 = [[Person alloc] init];
    person3.firstName = "Ralph";
    person3.lastName = "Johnson";

    self.people = @[ person1, person2, person3 ];
  return self;

Implement the relevant UITableViewDataSource delegate methods:

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

- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
  return self.people.count;

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
  UITextField* firstNameField;
  UITextField* lastNameField;

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier"];
  if (cell == nil)
    //Create a new cell
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CellIdentifier"];

    //Create a new first name field
    CGRect firstNameFrame = CGRectMake(0, 0, 150, 44);
    firstNameField = [[UITextField alloc] initWithFrame:firstNameFrame];
    firstNameField.delegate = self;
    firstNameField.tag = 1;
    [cell.contentView addSubview:firstNameField];

    //Create a new last name field
    CGRect lastNameFrame = CGRectMake(150, 0, 150, 44);
    lastNameField = [[UITextField alloc] initWithFrame:lastNameFrame];
    lastNameField.delegate = self;
    lastNameField.tag = 2;
    [cell.contentView addSubview:lastNameField;
    //Reuse the old text fields
    firstNameField = (UITextField*)[cell.contentView viewWithTag:1];
    lastNameField = (UITextField*)[cell.contentView viewWithTag:2];

  //Update the text field values for this cell
  Person* person = self.people[indexPath.row];
  firstNameField.text = person.firstName;
  lastNameField.text = person.lastName;

  return cell;

A couple of things to point out here. First, we only create the UITextField objects when dequeueReusableCellWithIdentifier: returns nil. Otherwise we would keep creating text fields that would overlap the old ones. However, we do update the actual text field values every time. Also, we assign each text field a unique tag property so that we can identify it later when updating the data source. Here is our override of the textFieldDidEndEditing: method we created earlier!

- (void)textFieldDidEndEditing:(UITextField*)textField inRowAtIndexPath:(NSIndexPath*)indexPath
  CQPerson* person = self.people[indexPath.row];
  switch (textField.tag)
    case 1:
      person.firstName = textField.text;
    case 2:
      person.lastName = textField.text;

That was easy! We simply use the tag property to identify what text field was updated and the indexPath to identify the cell it came from. Then we update our data source, and our EditableTableViewController takes care of the rest!

having issues?

We have a Questions and Answer section where you can ask your iOS Development questions to thousands of iOS Developers.

Ask Question

FREE Download!

Get your FREE Swift 2 Cheat Sheet and quick reference guide PDF download when you sign up to SwiftMonthly

Sharing is caring

If you enjoyed this tutorial, please help us and others by sharing using one of the social media buttons below.

Written by:

Martin is a New York area software developer and MBA with 10+ years of experience on the Microsoft stack. Over the past few years he has also expanded into iOS development using native Objective-C. he architects and develops full-stack web applications, iOS apps, database systems, and backend services.