You are on page 1of 5

Cocoa with Love: Easy custom UITableView drawing

09.04.2010, 14:56

Home

About

ProjectsWithLove.com

Easy custom UITableView drawing


It is really easy to customize your UITableView s. I'll show you how to completely customize the appearance of UITableView s without overriding or subclassing and without the need for any tricky hackery.

Search
Search

Make my table pretty


The core of most iPhone applications is the UITableView . To make your iPhone application stand out, the simplest way is to make your UITableView look good. Customizing your UITableView can be really easy. You don't need custom drawing code. You don't need subclasses of anything. Cocoa Touch provides all the drawing capability you need, all you have to do is use the right classes in the right ways and provide the layout.

Sponsors

The sample application


The approach I'll show you will turn the table on the left into the table on the right:

Archive
! 2010 (16) " 2009 (56) ! December 2009 (5) ! November 2009 (5) ! October 2009 (5) ! September 2009 (6) ! August 2009 (5) ! July 2009 (5) ! June 2009 (4) ! May 2009 (4) " April 2009 (4) Easy custom UITableView drawing What does it mean when you assign [super init] to ... Showing a "Loading..." message over the iPhone ke... 8 Confusing Objective-C Warnings and Errors ! March 2009 (5) ! February 2009 (4) ! January 2009 (4) ! 2008 (47)

Left: a default UITableView with three rows. Right: the same table view after customization.

How to fail at UITableView customizing


Coming from Mac OS X made it harder for me UITableView needs to be customized in a very particular way and structurally, it is very different to Mac OS X's NSTableView and NSCell drawing. The following are all really bad ways to customize a table (even though you can make it work): Subclassing UITableView to customize drawing Subclassing UITableViewCell to customize drawing Creating your own array of UITableViewCell s and returning these instead of using
dequeueReusableCellWithIdentifier:

Categories
AppKit (25) C++ (5) Cocoa Touch (21) Core Data (10) CoreAnimation (5) CoreAudio (3) CoreGraphics (4) Foundation (37) fun hacks (10) graphics (2) numerical methods (2) object-oriented design (15) Objective-C (27) performance tests (5) self promotion (6) Snow Leopard (4) Standard C (14) testing (5) UIKit (18) WebKit (2) Xcode (15)

About the second point: it is okay to customize UITableViewCell but you shouldn't really use it for drawing. The UITableViewCell class is more of a controller class it handles behaviors and layout, not drawing. You can customize UITableViewCell to load a specific contentView (and do the custom drawing there). That last point (that you should always use dequeueReusableCellWithIdentifier: ) is only peripherally related to drawing but it will significantly slow your drawing down if you avoid the normal cell queuing architecture.

How to succeed at UITableView customizing


There are only a few points to understand related to table drawing. First: the UITableView does not itself draw anything except the background. To customize the background of a UITableView , all you need to do is set its backgroundColor to [UIColor clearColor] and you can draw your own background in a view behind the UITableView . Second: The tableHeaderView (and the table footer and section headers and footers) need not be just a title. You can insert your own view, with its own subviews in the table header,

http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html

Page 1 of 5

Cocoa with Love: Easy custom UITableView drawing


giving layout and custom drawing freedom. Third : UITableViewCell is composed of 5 different subviews. Customizing the right subview is the secret to good UITableViewCell drawing. The subviews are: 1. backgroundView the entire background of the row (including what looks like the UITableView 's background in UITableViewStyleGrouped style tables. 2. selectedBackgroundView replaces the backgroundView when the row is selected. 3. image a customizable image (not actually a subview) at the left of the cell. 4. accessoryView a customizable view at the right of the cell. 5. contentView a customizable view between the image and the accessoryView (technically, it extends behind the image ). You can customize any of these (except image which must be a UIImage ) using your own custom drawn views. However, since the pixel size of the table never changes, it is often easiest just to use UIImageView s for each of them. Then you can take highly complex views drawn in separate programs, cut them into the 5 necessary pieces and let the automatic caching of UIImage 's named image cache manage your memory for you. There is an argument against drawing your views in code and that is that the iPhone's drawing is not nearly as fast as Mac OS X. Operations like gradients and multiple overlapped components can really tax the iPhone. Custom drawing code is a good choice for simple and flat colour drawing. In most other cases as in this post I recommend you use UIImageView to draw your views in a table.

09.04.2010, 14:56

Sponsors
The Mac Developer Network: Podcasts, Videos & Tutorials for Mac, iPhone & iPad Developers.

Syndication Feeds
Subscribe to this blog

Implementation
With all custom drawing handled by UIImageView , that still leaves some work to do. You must handle all layout and configuring of views.

Configuration of the UITableView and layout of the table header


As an example of what that means, have a look at the viewDidLoad method for this post:
- (v oi d) vi ew Di dL oa d { // // C ha ng e th e pr o pe r t i es o f t h e i m ag e V ie w an d ta b l eV i e w ( t h e s e c o u l d b e s e t // i n in te rf ac e b ui l d e r i n st e a d) . // ta bl eV ie w. se pa ra t or S t y le = U I T ab l e Vi e w Ce l l Se p a ra t o rS t y l e N o n e ; ta bl eV ie w. ro wH ei g ht = 10 0 ; ta bl eV ie w. ba ck gr o un d C o lo r = [ U IC o l or c le a r Co l o r] ; im ag eV ie w. im ag e = [ U I I ma g e i m a ge N a me d : @" g r ad i e nt B a ck g r o u n d . p n g " ] ; // // C re at e a he ad e r v i e w. W ra p it i n a co n t ai n e r t o a l l o w u s t o p o s i t i o n // i t be tt er . // UI Vi ew * co nt ai ne r Vi e w = [[ [U IV ie w al l oc ] in it Wi th F ra m e : CG R e ct M a ke ( 0 , 0 , 3 0 0 , 6 0 )] au to re le as e] ; UI La be l *h ea de rL a be l = [[ [U IL ab el a l lo c ] in it Wi th F ra m e : CG R e ct M a ke ( 1 0, 2 0, 3 00 , 40 ) ] au to re le as e] ; he ad er La be l. te xt = N S L oc a l iz e d St r i ng ( @ "H e a de r fo r th e t a b l e " , @ " " ) ; he ad er La be l. te xt C ol o r = [ U IC o l or w hi t e Co l o r] ; he ad er La be l. sh ad o wC o l o r = [U I C ol o r b l a ck C o lo r ] ; he ad er La be l. sh ad o wO f f s et = C G S iz e M ak e ( 0, 1 ); he ad er La be l. fo nt = [ U I Fo n t b o l dS y s te m F on t O fS i z e: 2 2 ]; he ad er La be l. ba ck g ro u n d Co l o r = [U I C ol o r c l e ar C o lo r ] ; [c on ta in er Vi ew a d dS u b v ie w : he a d er L a be l ] ; se lf .t ab le Vi ew .t a bl e H e ad e r Vi e w = c on t a in e r Vi e w ; }

This method handles the configuration of the tableView (setting the backgroundColor , rowHeight and sets an image behind the table) but also creates its own layout for the table header. The layout of the header here is for the table's header view. You can include a custom header for every table section by implementing the UITableViewDelegate method tableView:viewForHeaderInSection: . There are equivalent properties and methods for the table and section footers. It is possible to handle this type of layout in Interface Builder and load the XIB files for this type of layout. Sadly though, on the iPhone, reading loading lots of views from XIB files is slow (I suspect this is due to slow reading from the Flash memory) and doesn't always allow configuration of every property. For this reason, I normally sketch my views in Interface Builder and then manually recreate the same thing in code. That's what I've done here: picking coordinates for the headerLabel that looks balanced in the view.

Cell backgrounds
The cell background needs to incorporate the tops and bottoms of table "sections". For this reason, the backgroundView and selectedBackgroundView normally need to be set on a row-by-row basis. In your tableView:cellForRowAtIndexPath: method where you are configuring the cell for a given row, this code will handle that behavior:

http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html

Page 2 of 5

Cocoa with Love: Easy custom UITableView drawing

09.04.2010, 14:56

U IIm ag e *r ow Ba ck gr ou n d; U IIm ag e *s el ec ti on Ba c kg r o u nd ; N SIn te ge r se ct io nR ow s = [ a Ta b l eV i e w n u mb e r Of R o ws I n Se c t io n : [ i n d e x P a t h s e c t i o n ] ] ; N SIn te ge r ro w = [i nd e xP a t h r o w ]; i f ( ro w == 0 & & ro w = = s e c ti o n Ro w s - 1 ) { ro wB ac kg ro un d = [ UI I m a ge i ma g e Na m e d: @ " to p A nd B o tt o m Ro w . p n g " ] ; se le ct io nB ac kg ro u nd = [U I I ma g e i m a ge N a me d : @" t o pA n d Bo t t o m R o w S e l e c t e d . p n g " ] ; } e lse i f (r ow = = 0) { ro wB ac kg ro un d = [ UI I m a ge i ma g e Na m e d: @ " to p R ow . p ng " ] ; se le ct io nB ac kg ro u nd = [U I I ma g e i m a ge N a me d : @" t o pR o w Se l e c t e d . p n g " ] ; } e lse i f (r ow = = se ct i on R o w s - 1) { ro wB ac kg ro un d = [ UI I m a ge i ma g e Na m e d: @ " bo t t om R o w. p n g" ] ; se le ct io nB ac kg ro u nd = [U I I ma g e i m a ge N a me d : @" b o tt o m Ro w S e l e c t e d . p n g " ] ; } e lse { ro wB ac kg ro un d = [ UI I m a ge i ma g e Na m e d: @ " mi d d le R o w. p n g" ] ; se le ct io nB ac kg ro u nd = [U I I ma g e i m a ge N a me d : @" m i dd l e Ro w S e l e c t e d . p n g " ] ; } ( (UI Im ag eV ie w *) ce ll . ba c k g ro u n dV i e w) . i ma g e = r ow B a ck g r ou n d ; ( (UI Im ag eV ie w *) ce ll . se l e c te d B ac k g ro u n dV i e w) . i ma g e = s el e c t i o n B a c k g r o u n d ;

Layout within the contentView


Layout of elements within the contentView need only be set on construction of the contentView (not on a row-by-row basis). Sadly, laying out UILabel s in the contentView (like the "Cell at row X." and "Some other infomation." lables in this example) is a little verbose. The following code is run immediately after the allocation of the UITableViewCell to position the "Cell at row X." label:
c ons t CG Fl oa t LA BE L_ H EI G H T = 2 0; U IIm ag e *i ma ge = [ UI I ma g e im a g eN a m ed : @ "i m a ge A . pn g " ]; // / / C re at e th e la be l f or t h e t o p r o w o f t e x t // t opL ab el = [[ [U IL ab el a ll oc ] in it Wi th Fr am e : CG Re ct Ma k e( im ag e .s i z e .w i d th + 2 . 0 * c el l . in d e nt a t io n W i d t h , 0. 5 * ( a T a bl e V ie w . ro w H ei g h t - 2 * LA B E L_ H E I G H T ) , aT ab l eV i e w .b o u nd s . si z e .w i d th i ma g e . si z e .w i d th - 4 . 0 * c el l . in d e nt a t i o n W i d t h - in d i ca t o rI m a ge . s iz e . wi d t h, LA BE L _H E I G HT ) ] au to re le as e] ; [ cel l. co nt en tV ie w ad d Su b v i ew : t op L a be l ] ; // / / C on fi gu re t he p ro p er t i e s f o r t h e t e xt t ha t ar e th e sa m e o n e v e r y r o w // t opL ab el .t ag = T OP _L A BE L _ T AG ; t opL ab el .b ac kg ro un dC o lo r = [ U I Co l o r c l ea r C ol o r ]; t opL ab el .t ex tC ol or = [U I C o lo r co l o rW i t hR e d :0 . 2 5 g r ee n : 0. 0 b l u e : 0 . 0 a l p h a : 1 . 0 ] ; t opL ab el .h ig hl ig ht ed T ex t C o lo r = [ U IC o l or c ol o r Wi t h Re d : 1. 0 g r e e n : 1 . 0 b l u e : 0 . 9 a l p h a : 1 . 0 ] ; t opL ab el .f on t = [U IF o nt s y st e m Fo n t Of S i ze : [ UI F o nt l ab e l Fo n t S i z e ] ] ; // / / C re at e a ba ck gr ou n d i m a ge v ie w . // c ell .b ac kg ro un dV ie w = [ [ [ U II m a ge V i ew a ll o c ] i n it ] au t o re l e a s e ] ; c ell .s el ec te dB ac kg ro u nd V i e w = [[ [ U II m a ge V i ew a ll o c ] i n it ] a u t o r e l e a s e ] ;

In my mind, it seems like there should be a more efficient way to do this. I hold out the possibility that there is. This code spends most of its time working out where the label should be placed. It needs to go right of the image, left of the accessoryView , middle of the row but above the "Some other information." label.

Other adornments
The accessoryView is just a UIImageView . The cell.image is set as a property. These are extremely simple additions but they make the table cells far more impactful.

Conclusion
You can download the EasyCustomTable project as a zip file (60kb). The code includes a #define at the top that allows you to toggle the custom drawing on and off. None of this is particularly revolutionary (it is all in the iPhone documentation) but it is still easy to miss the properties and methods that make it easy. This does require custom images. If you've never drawn anything, now is a good time to learn inkscape (it's free and very good for the price). You could also use Adobe Illustrator but if you

http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html

Page 3 of 5

Cocoa with Love: Easy custom UITableView drawing


have that much money, pay an artist to draw it for you. Layout of the content in code is probably the weakest part of the approach I've presented. To make it easier, you can pre-layout everything in Interface Builder and copy the layout into code. For complicated layouts, you could even try using nib2objc to convert your XIB files to code automatically (although I've never done this, I'm just mentioning nib2objc because the idea is so cool). Share this post:

09.04.2010, 14:56

Posted by Matt Gallagher Tuesday, April 28, 2009 Filed in categories: Cocoa Touch, UIKit
Andy Interesting article. I agree that the best way to customise appearance on iPhone is to use prerendered images. FWIW I layout my table cells in Interface Builder, as it's by far the easiest way to get the correct layout. The nib only gets loaded once, so performance is not an issue in my experience. 28 aprilie 2009, 11:48:10 GMT+03:00 Like Reply akosma absolutely brilliant post! and also thanks for linking to nib2objc :) I developed it primarily to create custom UITableViewCells, as a matter of fact. 28 aprilie 2009, 13:42:37 GMT+03:00 Like Reply Bryan I definitely support using IB for laying out table cells. It's a little bit more of an abstraction but it makes changing the layout much easier. Also, I've noticed that custom section headers can really slow down performance and eat up memory if used improperly. For some reason they seem to be recomposed after every pixel move. 28 aprilie 2009, 14:04:35 GMT+03:00 Like Reply Fabian Subclassing UITableViewCell to customize drawing Why should that be bad? In my opinion this is essential in iPhone programming. You definately have to explain that a bit more in detail ;) 28 aprilie 2009, 14:51:57 GMT+03:00 Like Reply Matt Gallagher You should customize one of the 5 subviews I listed instead. UITableViewCell is really just a container for those subviews. If you subclass it directly, you will lose out on special behaviors that UITableViewCell achieves by manipulating its subviews. 28 aprilie 2009, 15:14:39 GMT+03:00 Like Reply Fabian Of course it's just a container. But your subclass can add subviews to the contentview, ... UITableViewCell is made for subclassing it. How'd you manage rearranging subviews without a custom subclass? You can subclass UITableViewCell without adding more main subviews. You can still add them to the contentview or set the image, ... I just don't get the point... 28 aprilie 2009, 15:26:50 GMT+03:00 Like Reply Guest Could not agree more with you in this case. I add views where I draw graphs etc. and looks fine to me. It just gives me the full flexibility 29 aprilie 2009, 23:23:37 GMT+03:00 Like Reply Fagian You definately need to use a spell checker 3 noiembrie 2009, 06:40:00 GMT+02:00 Like Reply Programming Enthusiast *definitely 10 noiembrie 2009, 23:38:25 GMT+02:00 Like Reply Liked by Guest Guest Guest 9 Guests Peter Easy custom NSTableView drawing - with love? Can anyone point me to a similar article dealing with custom NSTableView drawing? Of course i would prefer to read it at Cocoa with Love.. 28 aprilie 2009, 17:39:33 GMT+03:00 Like Reply jsd I subclass UITableViewCell for the very common case of needing to load the cell's image from the web. You absolutely want to do this asynchronously and it's dead easy with a custom UITableViewCell. Also recycling cells from your own cache is crucial in this case, otherwise you will have to load the images over and over again. I can never get the reusable cell mechanism to work properly anyway.

http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html

Page 4 of 5

Cocoa with Love: Easy custom UITableView drawing


28 aprilie 2009, 19:58:30 GMT+03:00 Like Reply kkrupovich Can you please point to any code example of cell caching? it's often mentioned on forums but never seen "in the wild" :) 2 mai 2009, 01:54:39 GMT+03:00 Like Reply Nicu Can you post something about customizing section headers and toolbars (plus the toolbar buttons) thanks! 28 aprilie 2009, 23:56:33 GMT+03:00 Like Reply Millz Matt, would you recommend against subclassing UITableViewCell, creating a XIB to represent the view of that new cell, connecting all the IBOutlets up for the widgets you make, and then passing in the model that the cell is meant to represent into the new subclass in order to setup the labels/images/etc.? To me this separation is pretty important - for instance, to show a "contact" cell my process would be to make a new ContactUITableViewCell, make a XIB with just a tableviewcell in it (of that ContactUI... type) and setup the widgets graphically. Then when it comes time to display a contact cell I grab the subclassed cell out of the xib (or get a reuseable one from the table) and pass it the Contact model it needs to show. 2 mai 2009, 15:58:03 GMT+03:00 Like Reply Liked by 2 Guests Matthew Fantastic tutorial, but one small problem. When trying to use this with re-ordering enabled a white vertical line appears to the left of the re-order icon. Also, is it possible for the delete icon to be moved to better fit a skinned design? Thanks. 11 mai 2009, 12:34:09 GMT+03:00 Like Reply

09.04.2010, 14:56

< Prev

Next >
Social Networking by

Leave a comment
Basic HTML formatting tags (<a>, <b>, <i>, and <pre>) are permitted in comments.

Newer Post

Home

Older Post

All content Matt Gallagher, all rights reserved. Email: matt@projectswithlove.com Code samples may be freely used in any programming project, commercial or otherwise, at your risk. See About for more.

http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html

Page 5 of 5

You might also like