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). In this case, I’m using custom controller’s for my views (these controllers are a subclass of NSObject, not UIViewController) because I want to design my cells and wire my outlets in Interface Builder (you could easily omit the controller aspect altogether).

*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 our reusable view’s (controllers) in:

MasterTableController.h

@property (strong, nonatomic)  NSMutableDictionary *reusableControllers;

-  (void)registerController:(Controller *)controller forReuseIdentifier:(NSString *)reuseIdentifier;
-   (Controller *)reusableControllerForReuseIdentifier:(NSString *)reuseIdentifier;

Let’s hop over to implementation:

MasterTableController.m

@synthesize reusableControllers = __reusableControllers;

Our first method registers an existing controller for reuse:

- (void)registerController :( Controller *)controller forReuseIdentifier:(NSString *)reuseIdentifier
{

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

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:controller];

}

The second method retrieves an available controller (along with it’s view). 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, enumerate our 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:

- (Controller *)reuseableControllerForIdentifier:(NSString *)reuseIdentifier
{

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

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

NSInteger indexOfAvailableController = [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 (indexOfAvailableController != NSNotFound)
{
Controller *availableController = [arrayOfViewsForIdentifier objectAtIndex:indexOfAvailableController];
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”;

Controller *headerController = [self reusableControllerForIdentifierString:headerReuseIdentifier];

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

return [headerController view];

}

 

Lastly, don’t forget to nil out your reference to the reusableControllers 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>