
Designing for iOS: Taming UIButton - kaishin
http://robots.thoughtbot.com/post/33427366406/designing-for-ios-taming-uibutton
======
andymatuschak
It's really worth noting that there are substantial performance ramifications
to your choice here, in terms of CPU time for generation, memory usage, I/O,
and GPU time for rendering.

The resizable image background technique will be the fastest option (provided
you don't need to programmatically tweak the appearance). Next would be the
full-sized background (which needlessly wastes memory). Then the CG-based
approach, which could actually perform better than the full-sized background
if you had generated a resizable image—the technique presented here needlessly
wastes memory and CPU time by generating a full-width image. But it will still
perform worse in basically all cases than the resizable-image-from-disk
technique, so you would only want to do that if the parameters need to be
tweaked at runtime.

The CAGradientLayer-based approach (as written) is a very poor idea unless you
need the animated transitions because it requires extremely expensive off-
screen drawing passes due to the masking. If your situation permits you to use
"overdrawn" masking (as I discussed in WWDC 2012's "Polishing Your Rotation
Animations"), this would actually perform quite well—less memory consumption
than all the other options; small rendering cost each frame. See also WWDC
2011's "Understanding UIKit Rendering" for more on graphics performance with
UIKit.

I guess it's also worth noting that the cost of masking CALayers with a
borderRadius is much lower in iOS 6 than iOS 5, but don't go nuts: it's still
way higher than all these other options.

~~~
awolf
Great comment, thanks for making it. There's nothing like having a domain
expert explain the tradeoffs using specifics.

A while back I wrote a reusable class that draws a single button using _three_
overlapping CAGradientLayers each .5 pixels larger than the next layer drawn
on top of it. With this arrangement I can then specify top and bottom values
for the "outer gradient", "inner gradient" and "body gradient" producing
beautiful buttons that are 100% adjustable in code. Since 80% of my skills lie
in the development realm vs. the design realm this class has been insanely
useful for me across multiple projects.

But obviously not efficient. If I wanted to refactor my class to still have
the three levels of run-time rendered gradients along with rounded corners,
what would be the most efficient method?

~~~
andymatuschak
Render the gradients into a resizable UIImage via CG. That differs from
approach #2 in the article, which does the same thing, but generates a non-
resizable UIImage.

To make a resizable image at runtime, draw into an image as described in
approach #2, but make the image have width of left cap + 1 point + right cap,
then use -[UIImage resizableImageWithCapInsets:] to generate a wrapper with
the correct resizing behavior.

Then: make sure that if you have 100 buttons on-screen which all use the same
gradients, you end up reusing the same generated UIImage. You don't want to
redraw the same thing for each of them.

------
coob
Drawing controls in code, unless you gain something other than the space saved
for bitmaps, is a waste of time and energy. You gain nothing by drawing the
gradients for buttons in code.

If it all ends up in that format anyway, you might as well do what Apple does
95% of the time: just use bitmaps. In mobile, saving cycles is more important
than saving storage.

~~~
potatolicious
Except the ability to resize your UI widgets. If your UI is almost entirely
static, then sure, bundling a buttload of images is fast, easy on your
designers, and your engineers won't hate you.

But if your UI is of the sort that has highly variable sizing and layouts,
then you probably _do_ want to start writing custom draw code that removes you
from having to maintain a hundred variants of the same asset in your bundle -
say, multi-line gradient buttons. It also allows easier abstraction (say,
tinted gradient buttons) without your designer having to painstakingly
generate what is essentially the same asset save one feature.

Programmatic UIs also make it easier to do related A/B tests - otherwise
testing a button's color would involve shipping updates with _every_ candidate
in the bundle.

Like all things, be judicious and smart - though the trend I'm noticing is
that we're moving beyond simple UIs on iOS, and there's increasing demand for
the sort of UIs that demand this level of flexibility. The performance of
recent iOS devices have also been such huge leaps that the penalty of not
using pre-baked images everywhere is minimal in most cases.

Custom drawing also has a lot of optimization use - this is a surprising
little-known trick for custom UITableViewCells. Compositing is still a very
heavy load, avoid having complex view hierarchies for high-performance UI
components (a UITableViewCell is high perf in most instances). Consider
flattening your views into something that fits into your drawRect call -
drawing text directly instead of UILabels, drawing images directly instead of
UIImageViews, etc.

Also, a surprisingly little known trick is CALayer.shouldRasterize - setting
this flag will rasterize everything in that layer and below and cache it. This
incurs a heavier hit on redraw, but for objects that do not need to be redrawn
often it's worth it - this allows you to have baked-image performance while
still doing custom drawing.

~~~
dmishe
I this particular case a square stretchable image is everything that is
needed. True though if shape is different you are most likely left to do code

------
aaronbrethorst

        A recently introduced second option consists
        in using a resizable image as a button background
        after having set its resizable and non-resizable
        areas in code. Start by making a pill-shaped
        background image in your graphic editor.
    

Not accurate. -[UIImage stretchableImageWithLeftCapWidth:topCapHeight] has
been around since the first public SDK release. Although, I must say the
-resizableImageWithCapInsets: methods added in iOS 5 and 6 are far more
powerful.

Also, here are a bunch of UIButton subclasses that I think are pretty neat
that demonstrate some of the techniques described in the article, plus others:

<http://www.cocoacontrols.com/platforms/ios/controls/bbutton>

[http://www.cocoacontrols.com/platforms/ios/controls/psstoreb...](http://www.cocoacontrols.com/platforms/ios/controls/psstorebutton)

[http://www.cocoacontrols.com/platforms/ios/controls/gloss-
ca...](http://www.cocoacontrols.com/platforms/ios/controls/gloss-caustic-
shader)

------
DHowett
I'd like to throw in the minor nitpick that stretchable/capped images are not
iOS 5+, but in fact date back to iPhoneOS 2.0, with -[UIImage
stretchableImageWithLeftCapWidth:topCapHeight:]

All said and done, however, how is "here, use the APIs provided to you, or
draw the graphics yourself" at all "taming" UIButton?

------
millerm
Nice article. I'm still excited about getting my first beta of Pixate
(<http://www.pixate.com/>). I saw that kickstarter project here and bought in,
twice (they had lowered the goal). Then we'll have a new way of customizing
iOS controls. I did get the shirt, but I'd really the the beta. It should be
coming any day now.

~~~
spobo
You know about Nimbus CSS, right?
<http://docs.nimbuskit.info/group___nimbus_c_s_s.html>

~~~
millerm
Thanks for the link. I will have to take a harder look at it as it wasn't
quite obvious to me as to what it truly provides at the moment. Again, thanks
and I will clone it when I get to my laptop.

------
Inebas
Not connected to the tutorial but I was trying to look at their other posts
and I seem to get redirected to their learn.thoughtbot.com where they show
their books, webcasts, and workshops which isn't bad but I just want to read
their other posts.

Does anyone know how?

------
drp4929
Any thoughts on performance impact, on the app responsiveness, of each
approach ?

~~~
objclxt
It depends what you want to do - as has been alluded to here, the image based
approach will be the best option in terms of responsiveness. If you draw your
entire button with core graphics you can then rasterize it, but the drawing
process is in itself computationally more expensive than rendering out the
image.

------
allr
Any similar tutorial/article for styling UITableView? Always struggled with
custom cells (specially grouped cells)

~~~
k-mcgrady
Apple has good docs on it[1]. I find the easiest solution is to create a
custom cell nib and style that. If I need multiple custom cells I tend to do
it in code. This was something I always found difficult to do well but over
the years it's improved a lot (especially with the appearance API in iOS 5).

[1]
[http://developer.apple.com/library/ios/#documentation/UserEx...](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7)

