KCDKoala (Part 1)

on 2014/11/23 in Design

N. D. Zeltzer Share On GoogleShare On FacebookShare On Twitter

KCDKoala Logo

There are few tasks as common to iOS development as setting up a table view. As an iOS developer you have probably done this a hundred billion times; and you’re going to do it one hundred billion more. If you’re like me, you’ve developed some tools over the years to simplify the setup and customization of your table views.

KCDKoala is an open source re-implementation of my own solution to quickly setting up and working with table and collection views. It’s an object controller with UITableViewDataSource and UICollectionViewDataSource subclasses that exposes a common API for manipulating the underlying object collection.

I thought I would take a few blog posts to discuss KCDKoala’s design: there are a lot of iOS programming blogs out there that will teach you how to do things on the micro level, but I’ve come across very few that take a step back and discuss software development from a wider perspective. KCDKoala’s underpinnings aren’t all that complicated: if you wrote a book about how it does the things that it does, you’d have a dozen concepts spread over half as many chapters. But you still wouldn’t know the why or the how that went into developing it.

If you’re at all interested in writing useable code, the forks in the road, both taken and not, are more than details: they’re the raison d’être.

But I’m getting ahead of myself. For today, all I’m going to do is give a high level overview of what KCDKoala does.

Division of Responsibilities: UITableView, UITableViewDelegate, UITableViewDataSource

No matter how you approach table views, you’re going to be dealing with three critical components: UITableView, UITableViewDataSource, and UITableViewDelegate. You might get a jump start with UITableViewController, a UIViewController subclass that uses a UITableView instance for its view and implements the basic methods declared in the table view’s delegate and data source protocols. And if all you’ve ever worked with are UITableViewController subclasses, you might not have stopped to appreciate the different responsibilities assigned to the table view’s delegate and data source. But the distinction between these two protocols matters: Apple did a wonderful job of dividing responsibilities.

KCDKoala takes advantage of this division by providing a controller that works like a storage class, consuming the UITableView and UICollectionView data source protocols, and providing default, overridable, implementations of the corresponding delegate protocols.

From the highest level, it works like this:

  1. Your objects adopt either the KCDTableViewObject or KCDCollectionViewObject protocol either directly, or via a category.
  2. You initialize your view controller class with a custom initialization method – e.g., initWithStyle:objects:
  3. You get the reference to your view controller’s koala instance and interact with it like you would a mutable array:
KCDObjectController *koala = [self KCDObjectController];

id<KCDMutableSection>newSection1 = [koala insertSectionWithName:@"New Section 1" objects:myObjects1 animation:UITableViewRowAnimationFade];
id<KCDMutableSection>newSection2 = [koala insertSectionWithName:@"New Section 2" objects:myObjects2 animation:UITableViewRowAnimationFade];

[koala moveSectionAtIndex:1 toIndex:0];
[koala sortSectionAtIndex:0 withComparator:myComparator];
[koala moveObjectsAtIndexPaths:fromPaths toIndexPaths:toPaths];
[newSection1 enumerateObjectsUsingBlock:^(id<KCDObject> object, NSInteger idx, BOOL *stop) {
      [koala deleteObject:object animation:UITableViewRowAnimationMiddle];

The corresponding view will animate to reflect the updates.

Animated Updates

You effectuate view updates by manipulating storage. Simple. And if you’ve worked with iOS for long, or done anything with Cocoa Bindings on the Mac, obvious, familiar, and yawn-inducing.

But let’s rewind to step #1: protocol adoption. What’s that all about?

Reassignment of Responsibilities

In your typical UITableViewController and UICollectionViewController, you configure your views (i.e., cells) in your view controller. It’s the MVC norm. That’s how Apple does it, that’s how mom does it, that’s how George Washington did it. But kids these days are sticking all sorts of fancy stuff into table view cells – knobs, switches, sliders, clocks, radios, ponies – and the more stuff they stick in, the more work the view controller needs to do to manage the connection between the data a cell represents and the cell itself, and the more attenuated the connection between the underlying models and their views begins to feel.

KCDKoala provides an alternative approach: the objects represented in the table or collection view configure their own cells.

This responsibility is the core component of the KCDObject protocols:

// KCDTableViewObject

- (UITableViewCell *)cellForTableView:(UITableView *)tableView;
- (CGFloat)heightForTableView:(UITableView *)tableView;

// KCDCollectionViewObject

- (UICollectionViewCell *)cellForCollectionView:(UICollectionView *)collectionView;
- (CGSize)sizeForCollectionView:(UICollectionView *)collectionView;

Once an object adopts the appropriate protocol, presentation is effortless. And this behavior is completely optional: if you, as the API consumer, would prefer to serve up cells the traditional way – e.g., tableView:cellForRowAtIndexPath:– you can do so simply by implementing that method. In fact, any of the UITableView or UICollectionView delegate behaviors that KCDKoala implements for can be overridden simply by implementing them yourself: the koala will always defer to custom implementations of any delegate methods.

Finally, you can walk the line between the two behaviors by adopting the KCDTableViewDataSourceDelegate protocol, which provides you with the best of both worlds:

- (UITableViewCell *)koala:(KCDObjectController *)kola tableView:(UITableView *)tableView cellForObject:(id<KCDTableViewObject>)object atIndexPath:(NSIndexPath *)indexPath;

That’s all for now: next week I’ll discuss how asynchronous transactions are handled, the internal dispatch queue configuration, and how protocols are used to limit API availability to specific contexts.