Customizing Tables in MonoTouch

The source code for the completed example can be found here (for those that want to examine the code without running through the steps, or if you want to compare).

Why Custom Tables?

Tables are a staple in many iPhone applications. Although they’re certainly not a new invention by any means in terms of application development, tables in the iPhone are very specific, given the constraints of the size of the device.

Out of the box, Apple gives you quite a bit of ability to customize your tables through styles. When you first create a table, however, it looks pretty basic. Without any real customizations, there are two basic styles you get for your tables, the default style and the grouped style:

With a few tweaks to the table cells within the table, you can add images and accessories to each table cell as in:

You can even change the font and colors of the table cells, however, sometimes this just isn’t enough. If you truly want to break out of the box and create complex UIs, you have to create your own custom table cells. The following screen shot is an example of just that, an application that uses completely custom table cells to display content:

Fortunately for us, Apple gives us a pretty easy way to do this by allowing us to create our own custom UITableViewCell controls and use them in our UITableView controls. Let’s walk through creating an application that explores working with the UITableView and UITableViewCell controls to create the preceding examples.

Example Application

So let’s create the application. First, open up MonoTouch, it should look something like the following:

Next, create a new iPhone MonoTouch project. To do this, select File : New Solution:

Click “OK” on the Project Features dialogue, as they’re not relevant to what we’re doing:

Our project should now look something like this:

Open the MainWindow.xib file in Interface Builder by double-clicking on it in the Solution Explorer window, you should get something that looks like this:

Let’s start by dragging a UITableView control onto our MainWindow, it should now look like:

We need to add an outlet on our AppDelegate class, so that we can access this Table View programmatically.

To do this, in the Library window, select the “Classes” tab at the top, and then “Other Classes” in the drop-down. Then, make sure AppDelegate is selected, and click the “Outlets” tab in the second half of the Library window:

Let’s create an outlet called tblMain, by clicking the “+” button and typing in “tblMain” for the name, your outlets should now look like:

Next, let’s wire up the outlet to the UITableView we added. Select your AppDelegate in the document window, then in the Connections Inspector window, you should see the newly created tblMain outlet:

Wire this outlet up to the our table by dragging from the circle to the right of the outlet in the connections manager to our table in the Document window, or in the Window Designer:

Now that we got our table wired up, let’s switch back over to MonoDevelop and write some code.

Right-click on your project and choose “Add,” and then “New Folder”. Name the folder “Code.”

Next, a class named “BasicTableViewItem.cs.” To do this, right-click on the project again and choose “Add,” and then “New File.” Name the class and click “New:”

The UITableViewSource class is responsible for handling the data binding of our data to our table and also handling the user interaction with the table. In our case, handling user interaction is out of the scope of this article, but we’re still doing our data binding.

This class is a little more complicated than the other two, but let’s break it down:

Variable Declarations – We declare two variables, our _tableItems, which will hold the actual table items that populate the table, and the _cellIdentifier, which we will use later to provide a key in which to reuse our table cells.

Constructor – Our constructor takes a parameter called items of type List<BasicTableViewItemGroup>. In our example, we’re forcing our class to be instantiated with our groups of items, so that we know they’re valid.

NumberOfSections – This method is called by the table to retrieve the number of sections/groups to create in the table. In this case, we simply return the count of groups we have in our _tableItems.

RowsInSection – This method is called by the table to retrieve the number of items in a particular section/group to create in the table. We return the number of items in the particular group index.

TitleForHeader – This method is called by the table to retrieve the text that is displayed for the section/group header. We return the Name property for the BasicTableViewItemGroup in that section.

TitleForFooter – This method is called by the table to retrieve the text that is displayed below the section/group of items. We do the same thing here as TitleForHeader, except that this time, we return the Footer property.

GetCell – This is method is called by the table to retrieve the actual UITableCell control for a particular section/group and row, and is where the bulk of the work is done for data binding. The first thing we do here is to call DequeueReusableCell on the on the associated UITableView. This is done for performance reasons. Because the iPhone has limited processing power, if it had to create new UITableViewCell controls each time it had to display one, the overhead would cause a significant slow down on long lists. To counteract this, under the hood, the UITableView control keeps a pool of UITableViewCell controls, and when a cell scrolls out of view, it goes into the pool for reuse. This is where our _cellIdentifier comes into play. By giving our cell an identifier, they essentially become templates that we can reuse by pulling them out of the cell pool. At first, there won’t be any cells to reuse however, so in the next line, we check to see if the cell is null, and if it is, we create a new UITableViewCell control, specifying it’s style, and the CellIdentifier.

Ok, now that we’ve got those classes out of the way, we’re going to add some images so we’ve got something to display with the items.

First, let’s create a folder in the project called “Images.” Do this the same way you created the “Code” folder from before. Save the following images to your hard drive with the following names, respectively:

Image

Name

PawIcon.png

LightBulbIcon.png

PushPinIcon.png

TargetIcon.png

Once you have those saved, let’s add them to your project. Right-click on your “Images” folder and choose “Add Files.” Select the images that you saved, and check “Override default build action,” and select “Content” in the drop-down. This is very important. Content is the only build action that will allow you to load them and use them at run time.

Your solution should now look like this:

Make sure and build your project to make sure you’ve done everything correctly so far. To build, hold down the Apple Key and press the “B” button, or choose “Build All” from the Build menu.

All right, now that we have our classes and our images, let’s go edit our application code to use them!

Double click on Main.cs to bring the code editor up. In your AppDelegate class, add the following line:

BasicTableViewSource _tableViewSource;

This is a class variable to hold our BasicTableViewSource that we created earlier.

Let’s run the app and see what it looks like. To run the app, hold down the Apple key and press the “Enter” button, or “Debug” from the Run menu.

When we run it, we should see:

The footer looks a little weird in this mode. Let’s change the table type to Grouped and re-run it. To do this, let’s open the MainWindow.xib back up in Interface Builder (by double-clicking it), and edit the table’s style. Click on the Table View in the Document window, and then in the Inspector window, change the style drop-down to “Grouped:”

Save the file, switch back to MonoDevelop and run the application again, you should now see:

The footers now make a little more sense.

Let’s play with some other stuff. We, have an image and a sub-heading on our item, so let’s utilize them.

Stop the application and then double-click our BasicTableViewSource.cs class to edit it. Change this line, in our GetCell method:

There are two main parts to a UITableViewCell control, out of the box, the cell body, which is the entire area that the cell takes up, and the cell content, which is what we generally are allowed to put content in. If an accessory is not used, the cell content expands to take up the entire cell body. Similarily, if an image is not used, the label area overtakes the image are.

Let’s look at how we use this in our code. The first line tells the cell to have a Checkmark accessory in the right portion of the cell. Then we add our SubHeading text to the DetailTextLabel. Finally, we set an image on the cell.

It’s easy to create an image from our files. We simply use the UIImage.FromFile constructor, and pass it the path to our image. By setting our build type to Content on the images earlier, we can access them as if our folder structure in the project was our file system.

Running the application now, should get us the following:

Pretty cool. If we switch the table style in Interface Builder back to Plain from Grouped (open MainWindow.xib up by double-clicking, select our table, and change the style to “Plain” in the drop-down, and save), and run it again, we get:

Again pretty cool.

Since the 3.0 release of the iPhone OS, we’ve also been able to customize a number of other things including TextColor, SelectedTextColor, SelectedImage, BackgroundView, SelectedBackgroundView, and others. These give you a number of possibilities, but sometimes we want something radically different.

In order to accomplish this, we have to create our own table cell altogether. Apple allows us do this by inheriting from UITableViewCell and creating our own content, any way we want.

So let’s do just that. First, create a new folder in your project called “Controls.” In it, we’re going to create a new View with View Controller. Right-click on the Controls folder and select “Add,” and then “New File.” Choose “iPhone” from the left-pane and “View Interface Definition with Controller” on the right and name it “CustomTableViewCell:”

Open your CustomTableViewCell.xib file in Interface Builder by double-clicking it. It should be a blank view:

We don’t want a standard view, we want to create a UITableViewCell, so we’re going to delete the view and replace it with a cell.

Delete the View from your Document window by holding down the Apple key and press the Backspace key, or choose “Delete” from the edit window.

If you double-click on your newly added table cell, it’ll open up in the designer, and you can see it gives us a surface on which to add controls:

Let’s use Interface Builder to design our custom cell now. Make it a little taller by dragging the resize handle in the bottom right. Then, from the Library window, drag on a UIImageView and a couple of UILabels:

You can use the Attributes Inspector window to change the size of the UILabel controls, as well as the color, etc. It doesn’t have to look exactly like this, just as long as the elements are there.

Now that we have our custom design, let’s add outlets for our custom cell and it’s contents on the File’s Owner so that we can access them. Before, we added outlets directly to the App Delegate, but typically, we add them to File’s Owner, so that they get created on the class that owns the view.

To do this, select “File’s Owner” in the document window, then in the Library window, select the “Classes” tab at the top, and the “Other Classes” item in the drop-down. Select our CustomTableViewCell class, and then select the “Outlets” tab in the second half of the window. Add Outlets called “cellMain,” “lblHeading,” “lblSubHeading,” and “imgMain:”

Wire these outlets up to the controls that you previously created, just like you did before with our table.

Make sure “File’s Owner” is selected in the Document window and drag your control from the Connection Inspector to the control in the Document window:

The last thing we have to do in Interface Builder is to provide a cell identifier. Click on “Table View Cell” in the Document window, then in the Attributes Inspector, set the Identifier property to “MyCustomTableCellView:”

This allows our custom cell template to be reused, as we did before in code.

We’re done in Interface Builder, so save your file and then go back to MonoDevelop. Double-click on CustomTableCellView.xib.cs to open the class file.

We’re going to modify this class a little bit. First, let’s add the following properties to the class:

Cell – This property gives the consumer of our custom cell direct access to the cell itself, so that in the GetCell call in our UITableViewSource, we simple pull the cell directly from this property, as we’ll see in a bit.

Heading and SubHeading – These properties expose direct access to the text of our UILabel controls in our table cell.

Image – This property directly exposes the UIImage control of our table cell.

Next, we need to modify one of the constructors. The last constructor in file looks like this, when the file is created by MonoDevelop:

This seems a fairly straightforward constructor, if you read the Mono documentation on the base constructor that it’s calling (found here) it says that it initializes the class and loads the Nib (compiled .xib file) with the the name that we passed in. What the documentation fails to mention however, is that the base class constructor is asynchronous. If we use this call as is, the Nib will likely not actually get loaded immediately, and when we go to set our properties such as Heading, on our custom cell, we’ll get the dreaded null-reference error.

In order to fix that, we have to make sure the Nib file gets loaded synchronously, and the method doesn’t return until it has been loaded.

To do this, modify your constructor so that it looks like this.

public CustomTableViewCell ()// : base("CustomTableViewCell", null)
{
//---- this next line forces the loading of the xib file to be synchronous
MonoTouch.Foundation.NSBundle.MainBundle.LoadNib ("CustomTableViewCell", this, null);
Initialize ();
}

The first thing that we’re doing here is to comment out the call to the base-class constructor. We’re going to manually make this call, so we don’t need an extra one here. Next, we added a call to the LoadNib method. This does the exact same thing that our base-class constructor did, except that it forces it to be synchronous. Now, we’re assured that the Nib, and everything in it, will be initialized so that we can access it.

We’ve added a new item called _cellControllers. We’ll see how we use this later, but it will hold a collection of our customized cell controllers.

The next addition is the GetHeightForRow method:

/// <summary>
/// Called by the TableView to retreive the height of the row for the particular section and row
/// </summary>
public override float GetHeightForRow (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
return 109f;
}

When we created our custom cell controller, we made it taller than the usual cell. During databinding, CocoaTouch needs to know how much room to allocate for our cell. It does this by calling the GetHeightForRow method. If we didn’t tell it our new size, it would try to fit it in the standard size cell area, and we’d see weird results.

If we open up our cell in Interface Builder and look at the Size Inspector, it tells us the height of our cell. In my case, it’s 109 pixels, but yours may be slightly different.

This method starts out very similar to our BasicTableViewSource class, we try and pull a cell to reuse from the pool via the DequeueReusableCell call. After this, things get a little more complicated. If we can’t get a cell to reuse, we have to create a new one. This starts by creating the our CustomTableViewCell class which is the controller that actually contains our custom cell.

Once we’ve created our controller, we assign the cell variable to be the Cell property of our controller.

We then add a unique identifier to our cell, so that we can associate it with the correct CustomTableViewCell controller later. We’re using Environment.TickCount because it provides a reasonable level of uniqueness. If we wanted to, we could use the Guid.NewGuid, but that’s a fairly expensive call.

Next, we use that same tag identifier that we created to store the CustomTableViewCell controller in a Dictionary object.

If you change your table style to Grouped in Interface Builder, save your changes, and then run it again, you should get something like:

Congratulations! That was a lot of work, but you know how to customize tables with MonoTouch. Now that we’ve got this all working, let’s look at a few more considerations when working with custom cells.

Performance Considerations

If not done properly, using the UITableView can have tremendous performance issues. This is especially true if you have many rows. The iPhone, while impressive, has a limited processor, and in order to get the responsiveness that users expect with iPhone applications, there are several performance optimizations that you should consider.

Additionally, it is advisable to deploy to the device early and often and evaluate the performance of your table. The simulator is just that, a simulator, and not the actual device. It operates much faster than the device does, and if you rely on it to gauge the performance of your application, you will almost certainly be disappointed when you deploy to the device.

Furthermore, when you do test your tables, if the users can add their own rows, you should test your application with many more rows than you expect a user to add. If it is still snappy and responsive, you know that you’ve built it correctly, and your users won’t be disappointed with its performance.

Cell Reuse – Cell reuse is the first technique to employ to have a responsive table. This is especially true if you have lots of rows. If you do not reuse your cells, the iPhone OS has to create new ones each time one is displayed. This can become a huge problem very quickly. In order to determine if your cells are being reused, it is advisable to use a Console.WriteLine call in your cell reuse code to write out to the application console when a cell is being reused when it scrolls onto the page. Make sure you have enough rows that some first appear off the screen, so that when they scroll onto he screen, the OS has an opportunity to reuse old ones that have already scrolled off. The examples in this article show how to properly reuse cells, if your cells aren’t being reused, study the code samples and make sure your implementation is correct.

Cache the Row Height – The table can request the height of your rows quite often. In fact, it will likely request it, at least every time the cell is created, and in some cases can request it more often than that. If you are calculating the row height based on cell contents, be sure and cache that value, so you don’t have to recalculate it. You may want to create a property on your custom cell controller, in fact, that calculates, and then caches it.

Cache Images – If you’re using images, consider caching them so that they don't have to be loaded from a file each time they’re displayed. If you’re cells share images, consider putting them into a Dictionary object and pull them from there. Be wary of loading too many though. It’s not unusual to only have half of the iPhone’s useable memory at your disposal, so if you have a lot of images, you may have no choice to load them from file, or at least have a collection where you only store a certain number, and load the least used from files.

Stay Away from Transparency – One of the most expensive operations to perform on the iPhone is the rendering of transparency. There are two drawing systems on the iPhone, CoreGraphics, and CoreAnimation. CoreGraphics utilizes the GPU and CoreAnimation utilizes either the main processor, or the GPU, depending what it thinks will be fastest, and usually this is the GPU. The problem with this is that the iPhone’s GPU is not optimized for blending. Because of this, you should try and avoid transparency where possible. If you absolutely cannot avoid transparency, you should do the blending yourself by overriding the DrawRect function, and handling the drawing yourself, as we see in the next bullet point.

Manually Draw the Cell in DrawRect – As a last ditch effort for performance, you can override the DrawRect method and perform the drawing yourself. This has it’s drawbacks however. First, it can be technically complex, and second, it can utilize a LOT of memory. As such, it’s much more suited to tables that don’t have a lot of rows, but require lots of processing power to create. For more information on this technique, check out the fifth example in Apple’s sample app TableViewSuite, noted below.

Avoid Complex Graphical Computations – As with the transparency issue, stay away from graphical elements that require computation, such as a gradient that isn’t already baked into the an image your displaying.

Create your Cell Programmatically – If you’re running out of optimizations, and you’re still needing a performance boost, you can scrap the custom UITableViewCell that you created in Interface Builder altogether and hand-create the controls programmatically. For more information about building views programmatically, check out Craig Dunn’s blog post. For further reading on creating MonoTouch apps without Interface Builder, checkout the articles listed on MonoTouch.info about it, here: http://monotouch.info/Tags/NoIB

I got the first part of the tutorial working fine...but when I tried to create the custom cell (5 times) I can't seem to get it working. I get an object reference not set to an instance of an object exception whenever it hits this code block:

Re: Object reference not set to an intense of an object
by
Ron Herhuth

I got it working...I forgot the constructor change.

Question I'm trying to no avail to build a custom menu in ib and then try to show it over the current view...it should just occupy a fraction of the parent view. I can't seem to make this work...it shows in the center but the views controls won't show up. I need to figure out how to display the subview at a specified size and location...and display it's controls...do you know how I can achieve this? Do you know of any tutorials that illustrate this? Please note that I don't want to use the navigation controller and swap views...I want the view to be a floating menu over the current view.

Hi,I found this a very useful article, thanks for posting it. I tried to implement this as a subview of a view, so I had 2 subviews within one view, the view with the table appeared on the bottom view on a main view within the app (this is an iPad app). The table is constructed ok, but when I try to scroll down the table I get a:

First of all great article, really helped me for what I needed.I had a little issue, where it seems that when the field name of the cell was 'view', thenthe property would yield null, as if it wasn't loaded yet, but all other fields were loaded.So when I renamed the field name from 'view' to something else(All in Interface Builder of course) then it workedlike a charm.

When I added the Custom View it is not doing anything after doing a successfull build!! I have checked the code thoroughly against supplied code and debugged it and can't understand what is happening. Using Monodevelop 2.4.2 version. Would appreciate any input.