I’m a big fan of UITableViews, as a class they enjoy the distinction of being completely ubiquitous and  complete workhorses, whilst still managing to look gorgeous. But they must be treated delicately, for example best practice dictates that table’s cell views be reusable, and the architecture of UITableView reflects that. Recently, I wanted to create a  table view that made heavier use of section headers by allowing the user to “tap” a section header and only then disclose its rows (also ensuring that only the rows from one section are visible at any given time). It’s quite possible I’ll have many more section header views scrolling by at any moment than I will have actual rows, and so it occurred to me that a similar enhancement approach (it’s a flyweight pattern) might benefit my header (and footer) views as they’re being presented by the table view. My solution isn’t as sophisticated as what happens with cell queueing, but it still delivers big-time in Instruments, and only takes a few moments to implement.

It’d be pretty simple to implement this functionality in a shared cache singleton class, but for my purposes , I’m going to avoid creating any additional subclasses and just add a few methods to my UITableViewController (the one Apple gave me in my project template). Additionally, my header and footer views are coming from XIBs, and they’re wired up, so I’m caching views as well as their controllers because we will of course need to update labels as we scroll along.

*Note: This sample assumes use of automatic reference counting. If you run this code in a manually managed memory project, you’ll end up with quite a few leaks.

Our adventure begins at the landing point for a handful of Apple’s Xcode templates: An empty subclass of UITableViewController. Because we’re essentially storing views that our controller will be presenting, I create an NSMutableDictionary property on the class to store reusable controller’s in:

MasterTableController.h

@property (strong, nonatomic)  NSMutableDictionary *reusableViewControllers;

-  (void)registerViewController:(UIViewController *)viewController forReuseIdentifier:(NSString *)reuseIdentifier;
-   (UIViewController *)reusableViewControllerForReuseIdentifier:(NSString *)reuseIdentifier;

Let’s hop over to implementation:

MasterTableController.m

@synthesize reusableViewControllers = __reusableViewControllers;

Our first method registers an existing view controller for reuse:

- (void)registerViewController :( UIViewController *)viewController forReuseIdentifier:(NSString *)reuseIdentifier
{

NSMutableDictionary *reusableDictionary  = [self resuableViewControllers];
if (resuableDictionary == nil)
{
reusableDictionary = [[NSMutableDictionary alloc] init];  //creates a storage dictionary if one doesn’t exist
[self setReusableViewControllers];
}

NSMutableArray *arrayForIdentifier = [[self reusableViews] objectForKey:reuseIdentifier];
if (arrayForIdentifier == nil)
{
arrayForIdentifier = [[NSMutableArray alloc] init]; //creates an array to store views sharing a reuse identifier if one does not exist
[reusableDictionary setObject:arrayForIdentifier forKey:reuseIdentifier];
}

[arrayForIdentifier addObject:viewController];

}

The second method retrieves an available view controller. Because our table view controller doesn’t inform us as our section header & footer views scroll on and off the screen (we aren’t given opportunity to flag/file a controller as being available for reuse), we use a block, traverse all our  view controllers (don’t worry, there will never be more than the minimum quantity required to occupy the screen) and determine if any posses a view that isn’t currently on screen:

- (UIViewController *)reuseableViewControllerForIdentifier:(NSString *)reuseIdentifier
{

NSArray *arrayOfViewsForIdentifier = [[self reusableViewControllers]  objectForKey:reuseIdentifier];

if (arrayOfViewsForIdentifier == nil)
{
return nil;  //We don’t have any of this kind!
}

NSInteger indexOfAvailableViewController = [arrayOfViewsForIdentifier indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
{
return  [[obj view] superview] == nil;   //If my view doesn’t have a superview, it’s not on-screen.
}];

if (indexOfAvailableViewController != NSNotFound)
{
UIViewController *availableController = [arrayOfViewsForIdentifier objectAtIndex:indexOfAvailableViewController];
return availableController;    //Success!
}

return nil;

}

And that, my friends, is that. Because I’m feeling verbose, I’ll toss in a sample method demonstrating use. Here’s our template method providing our table it’s headerView:

-  (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{

static NSString *headerReuseIdentifier = @”headerView”;

UIViewController *headerViewController = [self reusableViewControllerForIdentifierString:headerReuseIdentifier];

if(headerViewController == nil) //Didn’t locate a reusable
{
headerViewController = [[UIViewController alloc] initWithNib:@”MyHeaderViewNib” bundle:nil];
[self registerViewController:headerViewController forReuseIdentifier:headerReuseIdentifier];
}

return [headerViewController view];

}

 

Lastly, don’t forget to nil out your reference to the reusableViewControllers property on your class when you unload. You could additionally empty the dictionary in low-memory conditions.

 

 

 

 

Tagged with:
 

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>