UITableViewController’s current architecture leads to bloated and disorganized table view controllers because it becomes a hub for different types of code, including:
- View controller logic
- Initializing table view cells
- Presenting table view cells
- Downloading images
- Fetching or loading data to populate table view cells
This added complexity makes it hard to manage table view controllers with:
- Multiple different types of cells
- Multiple data sources, possibly from different services
- Intricate cells that vary within a table view (Feed cells)
- Cells that vary across related table views (Users who like an item, users to follow, users in search results)
CoffeeTableViewController is a modular, scalable and opinionated extension of UITableViewController.
CoffeeTableViewController separates UITableViewController code into organized modules.
The data source contains an array of objects each of which is displayed as a table view row via a designated presenter. The table view controller defines this mapping between an object in the data source and a presenter.
The data source contains code to fetch & populate the array of objects which represent the rows of the table view. Advanced table views with multiple sections are enabled via a two dimensional array. The data source communicates with the table view controller via a delegate.
Your custom data sources must inherit from CTableViewDataSource.
Classes that implement the presenter protocol contain code to initialize, present, update and handle interactions with table view cells. Presenters have an associaed style attribute which enables different presentations of the same table view cell by simply changing a parameter.
Your custom model presenter classes must implement CModelPresenterProtocol.
The table view controller contains an instance of the data source, view controller logic and a hash that maps data source objects to presenters.
Your custom table view controllers must inherit from CTableViewController.
This section highlights the concepts discussed above via the Basic project found in the Examples folder. The aim is to render this simple table view of users:
The UsersViewDataSource inherits from CTableViewDataSource and it’s sole aim is to populate an array of objects that represents rows in the table view.
The [self addObjects:users]
call populates this array of objects and [self.delegate reloadTableView]
asks the table view to re-render.
- (void)loadUsers { NSMutableArray *users = [NSMutableArray arrayWithCapacity:10]; //Populate users array - remotely or locally [self addObjects:users]; [self.delegate reloadTableView]; }
Most CTableViewDataSource methods contain a sectionIndex
parameter that enables support for table views with multiple sections.
The UserPresenter class implements CModelPresenterProtocol and wraps around the code to use UserCell for displaying users populated via UsersViewDataSource. The primary functions of the UserPresenter are:
The presenation style parameter is defined by the UsersViewController along with mapping of User objects to UserCell views.
+ (UITableViewCell*)cellForObject:(id)object withBaseCell:(id)base withCellIdentifier:(NSString*)identifier withDelegate:(id)delegate andPresentationStyle:(NSInteger)style { User *user = object; UserCell *cell = base; //Initialization if(!cell) cell = [[UserCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:identifier]; // Presentation [user downloadImage]; [cell setUserImage:user.image]; if(style == kUserPresenterStyleWithByline) [cell setUserName:user.name andMessage:user.byline]; else [cell setUserName:user.name]; return cell; }
The UsersViewController is responsible for view controller logic and can update the presentation of visible cells by passing them an updated object. For example, if a User image is downloaded the UsersViewController can update visible UserCells by:
[self provideResourceToVisibleCells:user updatedKey:@"image"];
The CTableViewController bundles the updated object with each of the visible cells and calls:
+ (void)updatePresentationForCell:(id)base ofObject:(id)object withPresentationStyle:(NSInteger)style withUpdatedObject:(id)updatedObject andUpdatedKey:(NSString*)updatedKey { User *user = object; UserCell *cell = base; if(user == updatedObject) { if([updatedKey isEqualToString:@"image"]) [cell setUserImage:user.image]; } }
This facilitates a table view UI that responds immediately to changes in not only the object it represents but to any attribute of any object that the table view controller feels is relevant.
The UsersViewController is automatically assigned as the delegate for all UserCell interactions. The presenter acts as a facade to package and fire delegate events.
+ (void)cellClickedForObject:(id)object withBaseCell:(id)base withPresentationStyle:(NSInteger)style withDelegate:(id)delegate { SEL sel = @selector(userCellSelected:); if(![delegate respondsToSelector:sel]) return; User *user = object; [delegate performSelector:sel withObject:user]; }
The UsersViewController inherits from CTableViewController and primarily contains view logic. The primary functions of the UsersViewController are:
self.tableViewDataSource = [[UsersViewDataSource alloc] init]; [(UsersViewDataSource*)self.tableViewDataSource loadUsers];
[self addModelPresenterForClass:[User class] withStyle:kUserPresenterStyleWithByline withPresenter:[UserPresenter class]];
Displaying the UserCell without a byline is as simple as replacing kUserPresenterStyleWithByline
with kModelPresenterDefaultStyle
- (void)userCellSelected:(User*)user { NSLog(@"User clicked: %@",user.name); }
- (void)userImageLoaded:(NSNotification*)notification { [self provideResourceToVisibleCells:notification.object updatedKey:@"image"]; }
The Examples folder contains the Intermediate project which shows how CoffeeTableViewController works with more complex table views. This is the table view it renders.
Start by obtaining the source code, either by cloning the git repository:
git clone https://github.com/sidbatra/coffee-table-view-controller.git
or by downloading the latest zip or tar archive.
Add CoffeeTableViewController to your Xcode project:
- Drag and drop the CoffeeTableViewController folder into your project.
- Check the “Copy items into destination Group’s folder” and select Recursively create groups for any added folders.
and you’re done.
CTableViewController comes with loading, error and pull to refresh views. These views plug into the table view controller and can easily be replaced with your own views.
The CustomizedSupportingViews project in the Examples folder shows how to create a custom loading view. Create a custom view that implements the CLoadingViewProtocol
and place the following method in your table view controller.
- (UIView*)tableLoadingView { CGRect frame = self.view.frame; return [[CustomLoadingView alloc] initWithFrame:CGRectMake(0,0,frame.size.width,frame.size.height)]; }
That’s it. The error and pull to refresh views can similarly be customized by views that implement their specific protocols.
CoffeeTableViewController is built by the wonderful people who make Mine: http://getmine.com