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.