Objective-c – UITableView issue when using separate delegate/dataSource

cocoa-touchiphoneobjective-cuitableview

General Description:

To start with what works, I have a UITableView which has been placed onto an Xcode-generated view using Interface Builder. The view's File Owner is set to an Xcode-generated subclass of UIViewController. To this subclass I have added working implementations of numberOfSectionsInTableView: tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: and the Table View's dataSource and delegate are connected to this class via the File Owner in Interface Builder.

The above configuration works with no problems. The issue occurs when I want to move this Table View's dataSource and delegate-implementations out to a separate class, most likely because there are other controls on the View besides the Table View and I'd like to move the Table View-related code out to its own class. To accomplish this, I try the following:

  • Create a new subclass of UITableViewController in Xcode
  • Move the known-good implementations of numberOfSectionsInTableView:, tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: to the new subclass
  • Drag a UITableViewController to the top level of the existing XIB in InterfaceBuilder, delete the UIView/UITableView that are automatically created for this UITableViewController, then set the UITableViewController's class to match the new subclass
  • Remove the previously-working UITableView's existing dataSource and delegate connections and connect them to the new UITableViewController

When complete, I do not have a working UITableView. I end up with one of three outcomes which can seemingly happen at random:

  • When the UITableView loads, I get a runtime error indicating I am sending tableView:cellForRowAtIndexPath: to an object which does not recognize it
  • When the UITableView loads, the project breaks into the debugger without error
  • There is no error, but the UITableView does not appear

With some debugging and having created a basic project just to reproduce this issue, I am usually seeing the 3rd option above (no error but no visible table view). I added some NSLog calls and found that although numberOfSectionsInTableView: and numberOfRowsInSection: are both getting called, cellForRowAtIndexPath: is not. I am convinced I'm missing something really simple and was hoping the answer may be obvious to someone with more experience than I have. If this doesn't turn out to be an easy answer I would be happy to update with some code or a sample project. Thanks for your time!

Complete steps to reproduce:

  • Create a new iPhone OS, View-Based Application in Xcode and call it TableTest
  • Open TableTestViewController.xib in Interface Builder and drag a UITableView onto the provided view surface.
  • Connect the UITableView's dataSource and delegate-outlets to File's Owner, which should already represent the TableTestViewController-class. Save your changes
  • Back in Xcode, add the following code to TableTestViewController.m:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSLog(@"Returning num sections");
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSLog(@"Returning num rows");
    return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"Trying to return cell");
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    }
    cell.text = @"Hello";
    NSLog(@"Returning cell");
    return cell;
}
  • Build and Go, and you should see the word Hello appear in the UITableView

  • Now to attempt to move this UITableView's logic out to a separate class, first create a new file in Xcode, choosing UITableViewController subclass and calling the class TableTestTableViewController

  • Remove the above code snippet from TableTestViewController.m and place it into TableTestTableViewController.m, replacing the default implementation of these three methods with ours.
  • Back in Interface Builder within the same TableTestViewController.xib-file, drag a UITableViewController into the main IB window and delete the new UITableView object that automatically came with it
  • Set the class for this new UITableViewController to TableTestTableViewController
  • Remove the dataSource and delegate bindings from the existing, previously-working UITableView and reconnect the same two bindings to the new TableTestTableViewController we created.
  • Save changes, Build and Go, and if you're getting the results I'm getting, note the UITableView no longer functions properly

Solution:
With some more troubleshooting and some assistance from the iPhone Developer Forums, I've documented a solution! The main UIViewController subclass of the project needs an outlet pointing to the UITableViewController instance. To accomplish this, simply add the following to the primary view's header (TableTestViewController.h):

#import "TableTestTableViewController.h"

and

IBOutlet TableTestTableViewController *myTableViewController;

Then, in Interface Builder, connect the new outlet from File's Owner to TableTestTableViewController in the main IB window. No changes are necessary in the UI part of the XIB. Simply having this outlet in place, even though no user code directly uses it, resolves the problem completely. Thanks to those who've helped and credit goes to BaldEagle on the iPhone Developer Forums for finding the solution.

Best Solution

I followed your steps, recreated the project and ran into the same problem. Basically you are almost there. There are 2 things missing (once fixed it works):

  • You need to connect the tableView of the TableTestTableViewController to the UITableView you have on the screen. As I said before because it is not IBOutlet you can override the tableView property and make it and IBOutlet:

    @interface TableTestTableViewController : UITableViewController {
        UITableView *tableView;
    }
    
    @property (nonatomic, retain) IBOutlet UITableView *tableView;
    
  • Next thing is to add a reference to the TableTestTableViewController and retain it in the TableTestViewController. Otherwise your TableTestTableViewController may be released (after loading the nib with nothing hanging on to it.) and that is why you are seeing the erratic results, crashes or nothing showing. To do that add:

    @interface TableTestViewController : UIViewController {
        TableTestTableViewController *tableViewController;
    }
    
    @property (nonatomic, retain) IBOutlet TableTestTableViewController  *tableViewController;
    

    and connect that in the Interface Builder to the TableTestTableViewController instance.

With the above this worked fine on my machine.

Also I think it would be good to state the motivation behind all this (instead of just using the UITableViewController with its own UITableView). In my case it was to use other views that just the UITableView on the same screenful of content. So I can add other UILabels or UIImages under UIView and show the UITableView under them or above them.