Reduce Line of Codes to Half with MwfTableViewController

In this blog post, I am going to talk about implementing UITableViewController using open-source extension MwfTableViewController that I have implemented.

The posts will be published in multiple parts. Part 1 will cover the basic use case. Part 2 and more (have not been decided) will cover more advanced use cases.

Introduction

UITableViewController is probably one of the most commonly used controller in iOS apps. If you are an iOS developer, chances are you have already used it before.

I am not sure about anyone else, but I find out that it’s easy to repeat same if not similar codes in table view controller implementations. Many of them seems very much like boilerplates. But my concern is that it makes the controller class fat, ugly and very hard to maintain.

I want to put an end to this (or at least improve the situation), so I decided to write an extension to UITableViewController that would make implementing table view more pleasant and maintainable. The objectives are to reduce line-of-codes and improve organization (maintainability) of the codes.

As the time of writing this, MwfTableViewController is at version 0.0.4 and it’s being hosted at Github. If you are using Cocoapods, you can find the Specs here. The features can be summarized as followed:

  • Provide implementation of backing store for table data, at the same time provide better way to updates table content
  • Provide better design to create and configure table row cells
  • Provide improved support for search implementation
  • 100% unit-tested

Throughout this post, I’m going to use reference to Apple’s TableSearch sample codes, and provide the example codes as if it would have been written using MwfTableViewController.

Backing Store for Table Data

Let’s face it, using NSArray of object or NSArray of NSArray of object (table with sections) as table’s backing store for table data is not the best way to do it.

In my opinion, NSArray and UITableView’s datasource suffers some degrees of mismatch on the way it works. It’s most obvious when you need to perform batch updates to UITableView (insertion and deletion of rows). On NSArray, insertion or deletion of object affects the subsequent index of update operation. But when propagating the changes to UITableView, those indexes must be translated to NSIndexPath and/or NSIndexSet by adhering to rules. If you make mistake in translating them, your app would crash, not good!

If you are using NSArray as your backing store, you will always have to repeat this same codes to bridge those mismatch. So why not create backing store that work the same way UITableView datasource works so you can focus on your application logic instead.

That exactly what MwfTableData is for. You can either use this class on your UITableViewController implementation or as part of MwfTableViewController. The following will show example on the latter.

The API

MwfTableData API is grouped in 5 categories: creation, accessing data, inserting data, deleting data and updating data. Check MwfTableViewController.h file to find out about the methods.

Using with MwfTableViewController

Creating and providing initial data

Override method `createAndInitTableData`.

-(MwfTableData *)createAndInitTableData;
{
  MwfTableData * tableData = [MwfTableData createTableData];
  [tableData addRow:[Product productWithType:@"Device" name:@"iPhone"]];
  [tableData addRow:[Product productWithType:@"Device" name:@"iPod"]];
  [tableData addRow:[Product productWithType:@"Device" name:@"iPod touch"]];
  [tableData addRow:[Product productWithType:@"Desktop" name:@"iMac"]];
  [tableData addRow:[Product productWithType:@"Desktop" name:@"Mac Pro"]];
  [tableData addRow:[Product productWithType:@"Portable" name:@"iBook"]];
  [tableData addRow:[Product productWithType:@"Portable" name:@"MacBook"]];
  [tableData addRow:[Product productWithType:@"Portable" name:@"MacBook Pro"]];
  [tableData addRow:[Product productWithType:@"Portable" name:@"PowerBook"]];
  return tableData;
}

Reload Table Data

If your application needs to reload the table with new data, it can be done by setting `tableData` property (you don’t have to call [tableView reloadData]). Example:

-(void)reloadProducts;
{
  MwfTableData * tableData = [MwfTableData createTableData];
  // populate with reloaded data
  self.tableData = tableData; // will reload the table view
}

Batch Updates

A super convenience method `performUpdates:` is provided. Using this method, all the required updates to sync with tableView will be handled by MwfTableViewController. Example:

-(void)loadMoreProducts;
{
  NSArray * moreProducts = …; // load the products
  [self performUpdates:^(MwfTableData * tableData) {
    for (Product * p in moreProducts) {
      [tableData addRow:p];
    }
  }];
}

Before we move on to next section, it’s also worth mentioning that you do not need to implement `numberOfSectionsInTableView:` and `tableView:numberOfRowsInSection:` methods because they are already implemented for you.
Note: The implementation also takes care of scenario when you enable the search function.

Providing Table View Cell

Below shows the typical implementation to provide table view cell.

-(UITableViewCell*)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)ip;
{
  UITableViewCell * cell = nil;
  id item = [listOfItems objectAtIndex:ip.row];
  if ([item isKindOf:[Product class]]) {
    // create and configure cell for Product
  } else if ([item isKindOf:[ProductGroup class]]) {
    // create and configure cell for ProductGroup
  } // … and so on…
  return cell;
}

So, What’s the problem with the example above? Well, as I said earier it’s ugly, really ugly. What worse is it’s really hard to maintain.

To solve this, MwfTableViewController takes charge of implementing the `tableView:cellForRowAtIndexPath:` method.
What you need to do is implement the creation and configuration method for each of the data types (e.g. Product and ProductGroup) by following very simple naming convention.

Creation Method

`tableView:cellFor<DataClassName>AtIndexPath:`
or you can use short form `$cellFor(<DataClassName>)`

Configuration Method

`tableView:configCell:for<DataClassName>:`
or you can use short form `$configCell(<DataClassName>,<CellClassName>)`

To use the `$` short forms, you must define `CK_SHORTHAND` before importing `MwfTableViewController`.

#define CK_SHORTHAND
#import "MwfTableViewController.h"

Example:

-$cellFor(Product);
{
  static NSString * CellIdentifier = @"ProductCell";
  UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (!cell) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
  }
  return cell;
}
-$configCell(Product,UITableViewCell);
{
  cell.textLabel.text = product.name;
}

That’s it, no more writing the endless and ugly if-else.
You can further improve your codes organization by implementing those methods in a objective-c category. By doing that, you keep your controller class lean and write the creation and configuration method for each data type ONCE.
Example:

@interface MwfTableViewController (TableSearchCells)
-$cellFor(Product);
-$configCell(Product,UITableViewCell);
-$cellFor(ProductGroup);
-$configCell(ProductGroup,ProductGroupCustomCell);
// … and so on
@end

Implementing Search

Using MwfTableViewController to implement search is dead simple. You basically just need to do 2 things:

Enable Search

By setting `wantSearch` property to YES.

-(id) initWithStyle:(UITableViewStyle)style;
{
  self = [super initWithStyle:style];
  if (self) {
    self.wantSearch = YES;
  }
  return self;
}

Override the create search results method

The method name is `createSearchResultsTableDataForSearchText:scope:`.

-(MwfTableData *)createSearchResultsTableDataForSearchText:(NSString *)searchText scope:(NSString *)scope;
{
  MwfTableData * resultData = [MwfTableData createTableData];
  NSUInteger count = self.tableData.numberOfRows;
  for (int i = 0; i < count; i++) {
    Product * product = [self.tableData objectForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
    NSComparisonResult result = [product.name compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
    if (result == NSOrderedSame) {
      [resultData addRow:product];
    }
  }
  return resultData;
}

So that’s it. That’s all you need to do to implement basic table view with search function using MwfTableViewController.

If you would compare, it actually cuts around 50% of codes you need to write. Less codes means less bugs.
And you also get more elegant codes that is easier to maintain.

Finally, thanks for reading this post. Please share if you like it. If you have questions or feedbacks, you can post them in comments.

Advertisements
3 comments
  1. Andrea said:

    Sorry for the OT but I don’t know where to ask about this so I’m leaving you a comment:
    What wordpress plugin did you use to embed the code into a article?!

    Thanks a lot!

    • meiwin said:

      Hi, no plugin involved. Just use <div style=”overflow:auto;”><pre> … source codes … </pre></div>.

    • Andrea said:

      Thank you and sorry again for the OT.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: