Creating custom UITableViewCell from XIBs – step by step tutorial

At this point loading custom views from XIB files is fairly well documented I think. Why then am I writing about this again? Well, all mentioned posts/guidelines describe only the cell’s ‘loading’/displaying in a table view parts, none of them really show how to interact with the cell in the parent view controller, how to, for example, let it know that a button located inside a cell has been touched, or an text field’s value has been changed/edited.

I’d like to walk you through the whole process.

In this tutorial I will show how to build a very simple app with one view controller inherited from UITableViewController with 5 rows (cells) loaded from XIB. Each cell will have a title, a subtitle and a text field that will allow users to enter a number. I’ll also demonstrate how to, with the use of protocols and delegates, handle data entered in each cell.

The final app is shown below, in the debugger output we can clearly see (in the red box) values entered and into which row/cell.

Let’s start…

1. Open Xcode and create new project using the ‘Empty Application’ template.

2. Let’s name it XIBTableCells. To simplify memory management I’ll be using Automatic Reference Counting.

The following screenshot shows all files created for us by default.

3. We need a view controller (inherited from UITableViewController) so let’s add one named MainTableViewControler:

Two new files are now added to our project:

4. Now let’s modify application delegate so our view controller is used as our application’s root view controller and displayed when the app starts.

We have to import our view controller class to our AppDelegate.h file.

#import "MainTableViewControler.h"

and add a new property for our view controller:

@property (strong, nonatomic) MainTableViewControler *mainTableViewController;

Our AppDelegate.h file now looks like this:

//
//  AppDelegate.h
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import <UIKit/UIKit.h>
#import "MainTableViewControler.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) MainTableViewControler *mainTableViewController;

@end

In AppDelegate.m file we have to now synthesize our property:

@synthesize mainTableViewController = _mainTableViewController;

Now let’s modify application’s didFinishLaunchingWithOptions. We have to create and assign our view controller to the application’s window’s rootViewController property. The code below shows the full AppDelegate.m code (created by default, but not used here methods, have been removed):

//
//  AppDelegate.m
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import "AppDelegate.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize mainTableViewController = _mainTableViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    
    self.mainTableViewController = [[MainTableViewControler alloc] initWithStyle:UITableViewStyleGrouped];
    self.window.rootViewController = self.mainTableViewController;
    
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Before we start our application for the very first time let’s find and implement/modify the following two methods in the MainTableViewControler.m file:

- numberOfSectionsInTableView (we need 1 section…)
- numberOfRowsInSection (…and 5 rows/cells)

so:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return 5;
}

We can test our application now:

It’s working but it is not very exciting…

5. Now, based on the solution vilcsak’s solution presented here: http://stackoverflow.com/questions/540345/how-do-you-load-custom-uitableviewcells-from-xib-files

let’s create a base class XIBBasedTableCell (I decided to use a bit more meaningful class name for the base class) for our custom, XIB based cells (File/New/New File):

Two new files have now been added to our project:

Edit the new files as follows:

//
//  XIBBasedTableCell.h
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import <Foundation/Foundation.h>

@interface XIBBasedTableCell : UITableViewCell

+(UITableViewCell *) cellFromNibNamed:(NSString *)nibName;

@end
//
//  XIBBasedTableCell.m
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import "XIBBasedTableCell.h"

@implementation XIBBasedTableCell

+ (XIBBasedTableCell *)cellFromNibNamed:(NSString *)nibName {
    
    NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL];
    NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
    XIBBasedTableCell *xibBasedCell = nil;
    NSObject* nibItem = nil;
    
    while ((nibItem = [nibEnumerator nextObject]) != nil) {
        if ([nibItem isKindOfClass:[XIBBasedTableCell class]]) {
            xibBasedCell = (XIBBasedTableCell *)nibItem;
            break; // we have a winner
        }
    }
    
    return xibBasedCell;
}

@end

6. Finally, add our XIB based cell (File/New/New File…) and name it TableCellWithNumber:

Now, we have to create cell’s XIB file itself (File/New/New File…):

The above steps should result in three new files in our project:

7. Before we start building our cell using the Interface Builder let’s add three following outlets and one action to our TableCellWithNumber class:

//
//  TableCellWithNumber.h
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import "XIBBasedTableCell.h"

@interface TableCellWithNumber : XIBBasedTableCell

@property (nonatomic, weak) IBOutlet UILabel *title;
@property (nonatomic, weak) IBOutlet UILabel *subTitle;
@property (nonatomic, weak) IBOutlet UITextField *numericTextField;

-(IBAction)textFieldValueDidChange:(id)sender;

@end

and them in the class’ .m file synthesize properties and implement the action:

//
//  TableCellWithNumber.m
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import "TableCellWithNumber.h"

@implementation TableCellWithNumber

@synthesize title = _title;
@synthesize subTitle = _subTitle;
@synthesize numericTextField = _numericTextField;

-(IBAction)textFieldValueDidChange:(id)sender {

}

@end

8. It’s time to build our cell in the Interface Builder. Select TableCellWithNumber.xib file and add Table View Cell to it. To quickly locate required objects (here Table View Cell) use Object Library’s search feature.

Make sure that cell’s Class property points to your custom class – TableCellWithNumber.
If we want to avoid performance problems we have to set cell’s reuse identifier as well.

Alternatively we can implement the reuseIdentifier method in out UITableViewCell subclass – TableCellWithNumber:

-(NSString *) reuseIdentifier {
    return @"TableCellWithNumberCellIdentifier";
}

As an experiment, you can remove this method, if you’ve added it, and also remove cell identifier added with the Interface Builder. Then try to load 1000 cells and check table view scrolling speed.
Now let’s add two UILabels, representing title and subtitle, and one UITextField for stored number to our cell:

The last one thing is to hook up our outlets and action with cell’s objects as shown on the screenshot below:

9. Let’s go back to our table view controller. In MainTableViewControler.h file we have to import our custom cell class:

//
//  MainTableViewControler.h
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import <UIKit/UIKit.h>
#import "TableCellWithNumber.h"

@interface MainTableViewControler : UITableViewController

@end

and in MainTableViewControler.m replace the cellForRowAtIndexPath method with the following one that uses our custom, XIB based cell:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"TableCellWithNumberCellIdentifier";
    
    TableCellWithNumber *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = (TableCellWithNumber *)[TableCellWithNumber cellFromNibNamed:@"TableCellWithNumber"];
    }
    
    // Configure the cell...
    cell.title.text = [NSString stringWithFormat:@"Row: %d", [indexPath row]];
    cell.subTitle.text = [NSString stringWithFormat:@"Section: %d", [indexPath section]];
    cell.numericTextField.tag = [indexPath row];
    return cell;
}

If we want to avoid performance problems the value of the CellIdentifier variable must match the one set in the Interface Builder (or used as the return value in the reuseIdentifier method mentioned earlier). In this case TableCellWithNumberCellIdentifier.

The following line is responsible for loading our cell from the XIB file:

cell = (TableCellWithNumber *)[TableCellWithNumber cellFromNibNamed:@"TableCellWithNumber"];

We can test run our application again. The results are shown below:

We’re almost there. Now we want to allow only numbers to be entered in our numeric text field, they should be rounded to two decimal places. We also want inform our table view controller which value has changed (in which row/cell).

10. Let’s start with data validation. We use NSScanner object to ensure only numbers are allowed. All non-numeric values will be replaced with the value 0.00. In the TableCellWithNumber.m file replace textFieldValueDidChange method with the following one:

-(IBAction)textFieldValueDidChange:(id)sender {
    NSScanner *scanner = [[NSScanner alloc] initWithString:self.numericTextField.text];
    
    float value;
    if (![scanner scanFloat:&value]){
        value = 0;
        self.numericTextField.text = @"0.00";
    } else {
        value = [self.numericTextField.text floatValue];
    }
    
    self.numericTextField.text = [NSString stringWithFormat:@"%.2f", value];    
}

If we start our application now we’ll see that only numbers are allowed and if we try to enter an invalid string, for example XXX it’ll be replaced with the nice 0.00 value.

11. Now, what about informing our table view controller what value has changed in which row/cell? Let’s add one more thing to our project a protocol – TableCellEditableProtocol (File/New/New File…):

A final new file has been added to our project:

The protocol is very simple, it specifies only one optional method – cellNumericValueDidChange:

//
//  TableCellEditableProtocol.h
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import <Foundation/Foundation.h>

@protocol TableCellEditable <NSObject>

@optional
- (void) cellNumericValueDidChange: (NSInteger) tag: (NSNumber *) value;
@end

12. In TableCellWithNumber.h file import TableCellEditableProtocol and add a delegate property. Our TableCellWithNumber.h now looks as follows:

//
//  TableCellWithNumber.h
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import "XIBBasedTableCell.h"
#import "TableCellEditableProtocol.h"

@interface TableCellWithNumber : XIBBasedTableCell {
    id <TableCellEditable> _delegate;
}

@property (nonatomic, strong) id delegate;

@property (nonatomic, weak) IBOutlet UILabel *title;
@property (nonatomic, weak) IBOutlet UILabel *subTitle;
@property (nonatomic, weak) IBOutlet UITextField *numericTextField;

-(IBAction)textFieldValueDidChange:(id)sender;

@end

remember to synthesize the delegate property in the TableCellWithNumber.m file:

@synthesize delegate = _delegate; 

and add the following line to the end of the textFieldValueDidChange method:

[self.delegate cellNumericValueDidChange:self.numericTextField.tag: [NSNumber numberWithFloat:value]];

13. Now let’s go back to MainTableViewControler.h file and indicate that our view controller implements the TableCellEditableProtocol. Our .h file now looks like this:

//
//  MainTableViewControler.h
//  XIBTableCells
//
//  Created by Damian on 10/03/2012.
//

#import <UIKit/UIKit.h>
#import "TableCellWithNumber.h"

@interface MainTableViewControler : UITableViewController <TableCellEditable>

@end

14. Now, in the controller’s implementation file (.m) we have to implement protocol’s method:

- (void) cellNumericValueDidChange:(NSInteger)tag :( NSNumber *)value {
    NSLog(@"%d,%.2f", tag, [value floatValue]);
}

15. We’re almost ready to test our application. The last this is to assign cell’s delegate property to our view controller. We’re doing this in the controller’s cellForRowAtIndexPath method. Now it looks as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"TableCellWithNumberCellIdentifier";
    
    TableCellWithNumber *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = (TableCellWithNumber *)[TableCellWithNumber cellFromNibNamed:@"TableCellWithNumber"];
    }
    
    // Configure the cell...
    cell.title.text = [NSString stringWithFormat:@"Row: %d", [indexPath row]];
    cell.subTitle.text = [NSString stringWithFormat:@"Section: %d", [indexPath section]];
    cell.numericTextField.tag = [indexPath row];
    cell.delegate = self;
    return cell;
}

cell.delegate = self line is the key element here.

16. We can now test our app.

Now whenever the user modifies a cell’s text field our view controller gets informed about it.
In the debug output above we can see coma separated pairs of numbers (see the red box). First one is representing row number of the updated cell, and the second one current value stored in the cell’s text field.

That’s it.

If you have any questions, suggestions or comments on this post, please join the discussion below!

I hope you found this post useful. If you did, please consider signing up for my newsletter to be notified of new blog posts (appropriate form can be found near the top of the page). Please also share this post by tweeting it or use one of the sharing buttons located at the bottom of the page.

You can download the complete project form GitHub repository.

In one of the next posts I’ll show some XIB based cells created using the above approach and used in real utility applications.

References:
http://stackoverflow.com/questions/540345/how-do-you-load-custom-uitableviewcells-from-xib-files

3 Thoughts on “Creating custom UITableViewCell from XIBs – step by step tutorial

  1. Pingback: Creating custom UITableViewCell from XIBs – examples « Sprite Bandits

  2. vinothsivanandam on September 28, 2013 at 8:14 AM said:

    hi sir thank u for this tutorial
    i have one doubt
    how can i delete rows of this table
    when i delete row of this table , value of the textfield till there
    so how can i delete those values

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Post Navigation