Adding a UITableView as a subview of UITableViewCell using Interface Builder

Adding a UITableview as a subview of a UITableViewCell??? Yes, you read it correctly and I typed it correctly. No mistakes have been made here. It sounds strange but once the idea came to my mind I had to do try it out to see if it was possible. I searched online and couldn’t find any help so I decided to write a tutorial. Here it is:

Prep work – Getting things ready

First, we shall prepare all the stuff that we would be working with in order to load a UITableView inside a UITableViewCell. Goes without saying but I’d say it anyway, create/open an Xcode project that has a UITableViewController subclass or has a UIViewController subclass with a UITableView in it.

Real Work

Making a custom UITableViewCell

In order to make a custom UITableViewCell, first of all, create a subclass of UITableViewCell, we’ll name it ‘TableViewCell’. In Xcode 4, File > New > New File and in the dialog select UIViewController subclass and in the ‘Next’ dialog type UITableViewCell in the ‘subclass’ text field.

Step 1: Adjust Objects and Properties in Xib File

Open the xib file of your UITabelViewCell subclass and incase you cannot change the size of the view, delete that view, add a new UIView, and adjust the height to the value you need. I made it 200 so that I can have eight 25 pi long cells inside this cell (25×8=200).

Next thing, change the class of the ‘View’ object. Select the ‘View’ object, open ‘Identity Inspector’, and type your subclass’s name i.e. ‘TableViewCell’.

After that change the ‘File’s Owner’ to the UITableViewController subclass (or the class that has the table view whose cell this is)

Next create the IBOutlet for the UITableView inside this cell in the header file for UITableViewCell’s subclass, let’s name it tableViewInsideCell. In XIB, connect the IBOutlet and set the delegate and dataSource of the tableViewInsideCell to the ‘View’ object (whose class we just changed to ‘TableViewCell’) not the File’s Owner. This is an important step, so be careful. We will only make one connection to the File’s Owner and that is going to be with the ‘View’ object of this xib.

(See how the dataSource, delegate and ref outlet connections are made with the ‘TableViewCell’ rather than the File’s Owner.)

Step 2: Implement the UITableView Delegate and DataSource in UITableViewCell subclass

In the header, add the protocol that this class is implementing, i.e. UITableView delegate and data source. So, TableViewCell.h would look like:

#import <UIKit/UIKit.h>

@interface TableViewCell : UITableViewCell <UITableViewDelegate, UITableViewDataSource> {
      NSDictionary *data;
}

@property (nonatomic, retain) IBOutlet UITableView *tableViewInsideCell;
@property (nonatomic, retain) NSDictionary *data;

@end

Note that there is an NSDictionary object in .h file. This will be used to transfer data from the UITableViewController subclass to the UITableViewCell subclass. Use this dictionary in a switch or an appropriate conditional statement to set appropriate data values for each cell inside the tableView:cellForRowAtIndexPath delegate method.

TableViewCell.m would have the UITableView delegate and dataSource methods. Here is what it would look like:

#import "TableViewCell.h"

@implementation TableViewCell

@synthesize tableViewInsideCell;
@synthesize data;

- (void)dealloc {
      [data release];
      [tableViewInsideCell release];
      [super dealloc];
}

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
      self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
      if (self) {
          // Custom initialization
      }
      return self;
}

#pragma mark - Table view data source
- (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 8;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
      static NSString *CellIdentifier = @"Cell";
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
      if (cell == nil) {
          cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 
                            reuseIdentifier:CellIdentifier] autorelease];
      }

      // Configure the cell...

     switch (indexPath.row) {
        case 0:
            cell.textLabel.text = @"Latitude";
            cell.detailTextLabel.text = [data objectForKey:@"Latitude"];
            break;

        case 1:
            cell.textLabel.text = @"Longitude";
            cell.detailTextLabel.text = [data objectForKey:@"Longitude"];
            break;

        case 2:
            cell.textLabel.text = @"Speed";
            cell.detailTextLabel.text = [data objectForKey:@"Speed"];
            break;

        case 3:
            cell.textLabel.text = @"Altitude";
            cell.detailTextLabel.text = [data objectForKey:@"Altitude"];
            break;

        case 4:
            cell.textLabel.text = @"Date";
            cell.detailTextLabel.text = [data objectForKey:@"Date"];
            break;

        case 5:
            cell.textLabel.text = @"Time";
            cell.detailTextLabel.text = [data objectForKey:@"Time"];
            break;

        case 6:
            cell.textLabel.text = @"Course";
            cell.detailTextLabel.text = [data objectForKey:@"Course"];
            break;

        case 7:
            cell.textLabel.text = @"Accuracy";
            cell.detailTextLabel.text = [data objectForKey:@"Accuracy"];
            break;

        default:
            break;
      }

      return cell;
}

#pragma mark - Table view delegate
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 25;
}
@end

Adding the custom UITableViewCell to the Tableview

In order to add this custom cell to our tableview, the first step is to make an outlet for ‘TableViewCell’ (your UITableViewCell’s subclass) inside your UITableViewController subclass. This is what the ‘TableViewController.h’ would look like:

#import <UIKit/UIKit.h>

@class TableViewCell;

@interface TableViewController : UITableViewController {
    NSArray *dataArray;
}

@property (nonatomic, retain) IBOutlet TableViewCell *tableViewCellWithTableView;

@end

Open TableViewCell.xib and make the ‘View’ object’s (whose class we changed to ‘TableViewCell’) connection with the File’s Owner. We are doing this because we changed the File’s Owner class to our UITableViewController subclass. So our cell’s outlet in the tableview controller header will be connected to the custom cell that we created

Making the connections is confusing but its following a simple rule; connections are made with the class that has the outlet declared in it. Here is the list of all connections made in TableViewCell.xib:

1. Custom cell’s View <-> File’s Owner | class: TableViewController (because we need the cell object in TableViewController class to assign in tableView:cellForRowAtIndexPath)

2. Tableview inside the cell <-> custom cell’s View | class: TableViewCell (because the tableView is inside the cell)

3. UITableViewDelegate & UITableViewDataSource <-> custom cell’s View | class: TableViewCell (because the tableview that is inside the cell has its delegate and data source implemented in our custom cell’s class. These two connections are very important, if these are made with the File’s Owner by mistake then remember the File’s Owner which is tableview controller is implementing its own delegate and data source for the top most tableview, so you wouldn’t get an error but also not any output)

Anyway, coming back. After making the connection in the xib, add the custom TableViewCell to the table view like so:

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

    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"TableViewCell" owner:self options:nil];
        tableViewCellWithTableView.data = [dataArray objectAtIndex:indexPath.row];
        tableViewCellWithTableView.tableViewInsideCell.allowsSelection = NO;
        cell = tableViewCellWithTableView;
        [cell setNeedsDisplay];
    }

    return cell;
}

Note that the cell needs to be loaded from the xib rather than by using the initWithStyle:reuseIdentifier method. Also note that the NSDictionary object called ‘data’ that was declared in the custom cell header is being assigned the appropriate element from the dataArray. The dataArray was initialized to have NSDictionary objects, like so:

    NSDictionary *one = [[[NSDictionary alloc] initWithObjectsAndKeys:@"13.861970", @"Latitude",
                          @"100.504250", @"Longitude",
                          @"50 kph", @"Speed",
                          @"1000 meters", @"Altitude",
                          @"12/04/2010", @"Date",
                          @"05:45 PM", @"Time",
                          @"North west", @"Course",
                          @"+/- 10 meters", @"Accuracy", nil] autorelease];

    NSDictionary *two = [[[NSDictionary alloc] initWithObjectsAndKeys:@"13.862470", @"Latitude",
                          @"100.501389", @"Longitude",
                          @"60 kph", @"Speed",
                          @"1050 meters", @"Altitude",
                          @"12/04/2010", @"Date",
                          @"06:15 PM", @"Time",
                          @"North", @"Course",
                          @"+/- 10 meters", @"Accuracy", nil] autorelease];

    NSDictionary *three = [[[NSDictionary alloc] initWithObjectsAndKeys:@"13.861970", @"Latitude",
                            @"100.504250", @"Longitude",
                            @"70 kph", @"Speed",
                            @"1000 meters", @"Altitude",
                            @"12/04/2010", @"Date",
                            @"06:35 PM", @"Time",
                            @"South", @"Course",
                            @"+/- 10 meters", @"Accuracy", nil] autorelease];

    NSDictionary *four = [[[NSDictionary alloc] initWithObjectsAndKeys:@"13.861970", @"Latitude",
                           @"100.504250", @"Longitude",
                           @"50 kph", @"Speed",
                           @"1050 meters", @"Altitude",
                           @"12/04/2010", @"Date",
                           @"06:55 PM", @"Time",
                           @"South West", @"Course",
                           @"+/- 10 meters", @"Accuracy", nil] autorelease];

    dataArray = [[NSArray alloc] initWithObjects:one, two, three, four, nil];

That is all, just remember to set the cell height to the height of your custom cell.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 200;
}


With the above dummy data, my table looked like this:

This is the most raw form and can be customized in many ways to look nicer. I hope someone out there finds it useful…

Trying this and actually succeeding in it made me realize that iOS SDK is flexible and any UIView subclass element to any other UIView subclass. So don’t be afraid to implement new ideas no matter how wild they might appear. For those eager to try wild stuff, have a look at ‘Pulse’, its a news reader app that won Apple’s Design Award 2011. This app has horizontally scrollable images and its developers explained that they have used UITableViews to create that. I might try that myself and post a tutorial :)
Update: Pulse-style scrolling tutorial has been published. Enjoy!

Update

Borut pointed out that our custom cells with UITableView inside them are not being reused. If we study the code carefully, we will find out that we are not assigning any reuseIdentifier because we are not using the default -initWithStyle:reuseIdentifier method for creating cells. The reuseIdentifier property of UITableViewCell is readonly , we cannot set it. A workaround is to create a getter method in our UITableViewCell subclass for the reuseIdentifier and use the same string being used to dequeue the reusable cells. In our case, this should do:

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

Note that in this tutorial we are using @”Cell” for dequeuing cells in both UITableViewCell subclass and UITableViewController subclass. This needs to return the one we are using in the UITableViewCOntroller subclass because we want to reuse this cell there.

Update 2

Thanks a lot for your appreciation. Since there were a lot of requests for code. I’ve uploaded it to dropbox. Here is the link:
http://dl.dropbox.com/u/17653793/TableViewInsideCell.zip

About these ads

34 Comments on “Adding a UITableView as a subview of UITableViewCell using Interface Builder”

  1. Hi,
    Thanks for the tutorial.
    Could you please upload the tutorial project to the blog post? Some of the steps are difficult to follow. Also are you using XCode 4.0 or XCode 3.2?

  2. Dev says:

    You can post the project in github and share the github link. Hope this helps.

  3. keyholiano says:

    Hi, amazing job!!! Where can I find the project? Thanks a lot

  4. Open TableViewCell.xib and make the ‘View’ object’s (whose class we changed to ‘TableViewCell’) connection with the File’s Owner. We are doing this because we changed the File’s Owner class to our UITableViewController subclass. So our cell’s outlet in the tableview controller header will be connected to the custom cell that we created

    Can you please break down these steps into further small steps. I am having really hard time connecting IBOUTLET tableViewCellWithTableView to TableViewCell.

    Is it possible to mail the project for reference?

    Thanks!

    • xs2bush says:

      Ill further break it down:

      1. Open TableViewCell.xib
      2. Click on ‘View’ and Open Identity inspector
      3. Change the class name to TableViewCell, previously it must be UIView.
      4. Click on File’s Owner and open identity inspector again.
      5. Change the class name to TableViewController, previously it must be TableViewCell
      6. Now open the ‘Connections inspector’ on file’s owner.
      7. You must see tableViewCellWithTableView outlet not connected to anything. Connect this to the ‘View’

      I hope this helps you. Ill email you the project for reference :)

      • Thanks a lot! I was able to make it out after few hours and carefully reading your post again.

        Thanks for the code as well. I will try to do it while reading the post first (Going to the pulse one now). In case I get stuck, will refer the code.

  5. Norman says:

    Hi,

    Thanks very much for the tutorial, very interesting. Unfortunately, I get a little lost between the setup in the prep work and Step 1.

    I take it you opened a View-based project with the name TableViewInsideCell. I can see in the first image that you have the App Delegate with this name but I don’t see the corresponding View Controller that should have been created. Instead you have TableViewController. Did you simply rename the files?

    Sorry, still a little new and trying to understand.

    • xs2bush says:

      Yup, used Refactor > Rename :) just to make things clear

      • Norman says:

        Thanks.

        I got stuck towards the end. “After making the connection in the xib, add the custom TableViewCell to the table view like so: …” This replaces the previous method in TableViewCell or goes into TableViewController? I take it the latter but I’m getting some errors …

        Would you mind sharing the project?

        Thanks,
        Norman

      • xs2bush says:

        This goes in TableViewController. I have emailed you the project. Happy Coding :)

  6. Peter says:

    Hi there

    Thanks very much for posting this! Fun stuff to play with.

    Would you mind emailing me the project aswel?

    Cheers

    • xs2bush says:

      Not at all. Im doing that rite now :)

      • Peter says:

        Thank you!

        Here are a few places I got stuck (may be obvious to some but I’m pretty new), hopefully this helps anyone else working through this.

        Step 2:
        NSString *cellData;
        should be
        NSDictionary *data;

        The dataArray gets initialised in viewDidLoad

        TableViewController.m needs
        – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
        {
        return [dataArray count];
        }

        Keep up the iOS posts!

      • xs2bush says:

        Oooops….thanks for pointing that out. Ive changed it now :)

  7. Aco dMArco says:

    Ok…i’m completely lost right on the creating IBOutlet. Mind to send me the sample project please, thank you.

    Oh btw, thank you for posting this tut…i’ve been looking for this sometimes now.

  8. afmo says:

    Great job.
    I am a bit new for iphone developing and got some error in this sample. mainly in cellForRowAtIndexPath.
    could you please sending me the project.
    thanks

  9. Peter Jarosz says:

    Can the user actually select a cell from the inner table using this approach?
    I tried another approach, but the inner cells are not selectable. didSelect never gets called for the inner UITableView allthough dataSource and delegate are set correctly and the table is looking nice.

  10. Vincent says:

    Great tutorial. Just what I needed. You think you can send me the project?

  11. Anand Singh says:

    Thanks a lot! I was able to make it out after few hours and carefully reading your post again.

  12. John Zl says:

    Nice tutorial, i am stuck in between. Would you be able to email the project to me. Thanks

  13. NIRAV says:

    Thanks ….. its nice….

  14. Nic says:

    Awesome post, that’s what i’m looking for!

  15. PrayForMojo82be says:

    Great Stuff!

    Just went through it myself in XCode 4.3.2 and I noticed that there’s a little change in the first step you say:
    “In Xcode 4, File > New > New File and in the dialog select UIViewController subclass and in the ‘Next’ dialog type UITableViewCell in the ‘subclass’ text field”

    Thing is that XCode doesn’t let you select the ‘UIViewController’ subclass anymore. In stead you create .h and .m file as a subclass for the UITableViewCell, after which you need to create a view to get .xib file.

    Being new to XCode and iOS, it took me a while to figure this out, but learnt a lot from this.

    Thanks for sharing!

  16. norman martinez says:

    Hi, how can I reply this example but with storyboard?

  17. Gregory Hill says:

    I’m curious about storyboarding, too. I’ve got the download working, but the xib architecture is notably different from how the tables would appear in a storyboard in Xcode 4.5. Have you updated this at all on your own?

    I’m planning on taking a shot at this on my own. If I get it working, then I’ll let you know.

    Nice job with the concept, by the way. I tried this on my own and have had all sorts of issues. I like the thinking behind your implementation.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 53 other followers