Patterns for Creating UITableViewCells

At one point or another, every UITableView programmer will outgrow the default set of UITableViewCells styles available to them, and they will be forced to customize the cells to provide a better user experience.

The initial temptation of every lazy programmer like myself is to do the bare minimum amount of work to obtain the desired effect. This path typically starts by realizing that you can add a subview to your cell, like this:

static UIImage logo = UIImage.FromFile ("logo.png");
UITableViewCell CreateCell ()
{
	var cell = new UITableViewCell (UITableViewCellStyle.Default, "key");
	var imageView = new UIImageView (logo) {
		Frame = new RectangleF (10, 10, 20, 20);
	};
	cell.ContentView.Add (imageView);
	return cell;
}
	

Although the above code works, and you can get away with it for simple tasks, it does not take into consideration cell reuse. UITableViews like to recycle cells on the screen which is fine as long as you do not need to use a different image on each cell, as all of a sudden, you will need to keep track of the imageView value.

In other cases, your GetCell method will get bloated with a lot of inside information about all possible customizations that you might have done in a previous instance, and you will have to undo those. Apple's UICatalog sample is packed with code like that, and so is my port of the same code.

And that is just not a decent way of living.

You are miserable, your users are miserable and everyone around you is miserable.

My preferred pattern, which has worked better for me is to create a derived cell class that tracks all of my properties, and centralizes the management of updating the properties of my cell.

Assuming that my cell will render the contents of an object called "MyData", this is what my pattern looks like for custom UITableViewCells:

//
// I create a view that renders my data, as this allows me to reuse
// the data rendering outside of a UITableViewCell context as well
//
public class MyDataView : UIView {
	MyData myData;

	public MyDataView (MyData myData)
	{
		Update (myData);
	}

	// Public method, that allows the code to externally update
	// what we are rendering.   
	public void Update (MyData myData)
	{
		this.myData = myData;
		SetNeedsDisplay ();
	}
}

//
// This is the actual UITableViewCell class
//
public class MyDataCell : UITableViewCell {
	MyDataView myDataView;

	public MyDataCell (MyData myData, NSString identKey) : base (UITableViewCellStyle.Default, identKey)
	{
		// Configure your cell here: selection style, colors, properties
		myDataView = new MyDataView (myData);
		ContentView.Add (myDataView);
	}

	public override void LayoutSubviews ()
	{
		base.LayoutSubviews ();
		myDataView.Frame = ContentView.Bounds;
		myDataView.SetNeedsDisplay ();
	}

	// Called by our client code when we get new data.
	public void UpdateCell (MyData newData)
	{
		myDataView.Update (newData);
	}
}

With the above pattern implemented, I can now add all of my view specific gadgetry into the MyDataView class, images, helper labels, or other views.

Then, the Update method needs to make sure that all of those extra views are updated when this method is invoked. All of the configuration for your cell needs to take place in this method, and nowhere else.

The client code that uses these views then looks like this:

class MyTableViewDataSource : UITableViewDataSource {
	public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
	{
		MyData myData = Lookup (indexPath);

		var cell = tableView.DequeueReusableCell (key);
		if (cell == null)
			cell = new MyDataCell (myData);
		else 
			cell.UpdateCell (myData);
		return cell;
	}

The Extra UIView

You might be thinking that creating the extra UIView is not really worth the effort, as you likely only need to apply a few customizations, and you got already most of your bang for the buck by creating your custom UITableViewCell.

You would be right.

The reason for creating a custom UIView, is that there might come a time when you want to do some custom drawing in your cell. Perhaps add a nice gradient on the background, or perhaps draw some shadows, or mix large fonts with small fonts.

It might be just a couple of small touch-ups, nothing too complicated, but just a little extra polish. By using a custom UIView, you can now spice up your view just a tiny bit, by overriding the Draw method:

public override void Draw (RectangleF rect)
{
	var context = UIGraphics.GetCurrentContext ();
	UIColor.White.SetColor ();
	context.FillRect (Bounds);
	context.DrawLinearGradient (myGradient, start, end, 0);
}

Creating a MonoTouch.Dialog Element

If you are like me, lazy, you would likely not be writing a lot of GetCell methods and large dispatch tables for your UITableViews, and instead you are using MonoTouch.Dialog.

MonoTouch.Dialog is an API that takes away the administrivia out of building UITableViews and lets you focus on the content. I discussed MonoTouch.Dialog last year on my old blog.

Once you have your own UITableViewCell, it is trivial to turn that into a MonoTouch.Dialog Element. You would do it like this:

public class MyDataElement : Element {
	static NSString key = new NSString ("myDataElement");
	public MyData MyData;

	public MyDataElement (MyData myData) : base (null)
	{
		MyData = myData;
	}

	public override UITableViewCell GetCell (UITableView tv)
	{
		var cell = tv.DequeueReusableCell (key) as MyDataCell;
		if (cell == null)
			cell = new MyDataCell (MyData, key);
		else
			cell.UpdateCell (MyData);
		return cell;
	}
}

With the code above, you have everything you need to make your custom cell to be used with MonoTouch.Dialog.

You can see the entire pattern in action in TweetStation's source code. The 300 or so lines of code in that file are responsible for rendering a tweet in TweetStation.

Posted on 18 Jan 2011 by Miguel de Icaza
This is a personal web page. Things said here do not represent the position of my employer.