Kevin McMahon


Fun with Monotouch and Multi-Level Table Views without Interface Builder

With the recent release of Monotouch, which enabled development for the iPhone/iPod Touch platform using C# and .Net, it seemed like the perfect time to take a swing at mobile app development. The strong appeal of being able to leverage my C# and .Net experience while scratching an itch that I have had for a few apps was more than enough to entice me to order a brand new Mac mini and start learning the Cocoa framework.

My pet app project requires some pretty basic multi-level tabular data visualization, so that was the first thing I attempted to tackle. I started my learning endeavor by reading the view controller programming guide from Apple's ADC and working through a few of the tutorials listed on the Monotouch site. One of the example tutorials listed was for a basic RSS reader. Armed with his guide, I got up and running with a basic table view allowed me to drill down one level deep and return to the root via navigation button. This was a great introduction and the easy to follow steps combined with the screenshots certainly hit the mark; however, this was not quite the layout I was looking for.

The data that I wanted to display calls for more than just a list-view-to-detail-view relationship that the RSS reader implemented. As a beginner trying to figure out how to wire up my custom table controllers to each other and the navigation controller all via Interface Builder resulted in a lot of thrashing and a lot of ugly code. I eventually was able to roughly approximate the behavior I wanted, but it was pretty obvious to me there had to be a better way.

I set out to see if I could find more Monotouch examples to see if I could pick up some tips and stumbled across Craig Dunn's blog. Craig not only put together apps for the Monospace and PDC conferences (and graciously provided the source) but also posted a Roget's Thesaurus app that was just what I was looking for. It was a simple app, but it had all the elements in play that I was looking for and an incredible diagram that tied it all together.

One of the key tips and tricks I picked up from Craig's code was how easy it was to cut out the Interface Builder from the process. I also generally tend to avoid using the designer whenever possible in Visual Studio but here it was a key factor in my understand. This isn't to say that I'd never use the Interface Builder again or that it doesn't have its uses because it does but there was too much magic going on in how Monotouch links up the XIBs to their backing C# classes. It wasn't until I started explicitly creating views and view controllers instead of trying to wire them up after the fact did I start to understand how things were supposed to interact.

To implement a custom UITableViewController you need to set two properties with custom classes and override a method which is invoked by the framework when the view controller is loaded. You'll need to set the TableDelegate and DataSource properties on the controller with your own implementations of UITableViewDelegate and UITableViewDataSource. The implementation of the table delegate allows you to customize how the table will respond to certain events and the data source implementation provides not only the data the table uses but also how that data is to be displayed within the table. Typically the ViewDidLoad method is overriden to perform the view setup work needed during initialization of the view controller. Within the method the TableDelegate and DataSource properties can be set.

Example setting the TableDelegate and DataSource via the ViewDidLoad method:

namespace MultiLevelTableView{    [MonoTouch.Foundation.Register("RootViewController")]    public partial class RootViewController : UITableViewController    {        class DataSource : UITableViewDataSource { / Impl Here / }        class TableDelegate : UITableViewDelegate { / Impl Here / }                public override void ViewDidLoad ()        {            base.ViewDidLoad ();                        TableView.Delegate = new TableDelegate (this);            TableView.DataSource = new DataSource (this);        }    }}

Example setting the properties during UITableView construction:

namespace MultiLevelTableView{    [MonoTouch.Foundation.Register("RootViewController")]    public partial class RootViewController : UITableViewController    {        UITableView tableView;                class DataSource : UITableViewDataSource { /* Impl Here */ }        class TableDelegate : UITableViewDelegate { /* Impl Here */ }                public override void ViewDidLoad ()        {            base.ViewDidLoad ();                        tableView = new UITableView()            {                Delegate = new TableViewDelegate(this),                DataSource = new TableViewDataSource(this),            };                        //You can set more properties here like size and            //location in the frame etc.                        this.View.AddSubview(tableView);        }    }}

The meat of the table view controller falls in the implementations of the DataSource and TableDelegate.

A basic DataSource setup involves overriding two methods used by parent controller.  The RowsInSection method tells how many rows need to be displayed in the table and the GetCell method returns a cell view which was customized for display by setting the text and display properties.

class DataSource : UITableViewDataSource{    static NSString kCellIdentifier = new NSString ("MyIdentifier");    RootViewController tvc;        public DataSource (RootViewController tvc)    {        this.tvc = tvc;    }        public override int RowsInSection (UITableView tableView, int section)    {        return tvc.RootData.Count;    }    public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)    {        var cell = tableView.DequeueReusableCell (kCellIdentifier);                if (cell == null)        {            cell = new UITableViewCell (UITableViewCellStyle.Default, kCellIdentifier);        }                cell.TextLabel.Text = tvc.RootData.ElementAt(indexPath.Row);        cell.Accessory = UITableViewCellAccessory.DetailDisclosureButton;        return cell;    }}

The TableDelegate is where the behavior of the table to various events is defined. It is here in which we load up the next view to display when a user clicks on a cell. A basic implementation of a table view delegate involves overriding the RowSelected method with instructions on what to do with the row now that it has been selected. If the node selected happened to be a leaf node for example we might load up the detail's view to display the nodes properties or if the node was the parent of more child nodes we would load another list view to continue to drill down into the data. In our case we want to drill down to the next layer of data which is managed by the SubGroupViewController.

class TableDelegate : UITableViewDelegate{    RootViewController tvc;    SubGroupViewController sgvc;        public TableDelegate (RootViewController tvc)    {        this.tvc = tvc;    }        public override void RowSelected (UITableView tableView, NSIndexPath indexPath)    {        // get info from selected row        string selectedGroup = tvc.RootData.ElementAt(indexPath.Row);                // load up new controller to display        if(sgvc == null)            sgvc = new SubGroupViewController(selectedGroup);                // push the controller on the navigation stack        tvc.NavigationController.PushViewController(sgvc, true);    }}

The hang up for me with using Interface Builder to try and get the multi-level table views wired together was mostly due to not being familiar with the objects I was dealing with and not understanding what properties are needed to be initialized so the framework can display them correctly. Approaching the problem via the Interface Builder created a lot of noise that made seeing what was really needed to wire these table view controllers with the navigation controller difficult. In this case the the pre-packaged templates and generic layouts provided by Monotouch and Interface Builder were more of a hindrance than help but that is to be expected while learning a new technology.

I've packaged up my implementation of a multi-level table view app and put it on github.  If anyone is looking for straight-forward, simple example of nesting table views hopefully this will help.

I also recommend checking out the following links which helped me immensely: - Alex York's Monotouch posts including the RSS reader  - Sabon Rai's Sample Code for UITableView - Craig Dunn's Blog