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.
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?
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.
I wish Apple would publish a list of common misconceptions including things like this. I see lots of code where tons of UI drawing is being done that could (and should) be done with images because it's "hardcore" and "fast". But no- it's over complicated and slow!
Many of these are places where people are caching rasterizations of deep layer hierarchies to avoid paying the compositing cost on a per-frame basis. But if you do that with drawRect:, you're using the CPU (not the GPU); if you use your own images, you're on the hook for your own caching, which you're likely to mess up. Such cases should probably use -[CALayer setShouldRasterize:] instead.
For more about this stuff, check out the two WWDC sessions I mentioned above.
It really just matters what you want to achieve, and if you are just rendering one button, all techniques perform fine.
What if you want buttons that can be any color? Then using resizable images doesn't work very well.
I use custom drawing code for one button in an app because I have a color wheel that let's you color all controls in the app to whatever theme you like. And for some other buttons, I use images to have more graphical control.
It's not really true that all techniques perform fine if you're rendering just one button.
If you're using a masked CALayer, and that layer's in a scroll view or is otherwise animating around the screen, there's a very real chance that you'll drop frames, just from that layer's off-screen rendering pass. Depends on how big the button is.
Certainly, though, if you need parameterizable imagery, you need parameterizable imagery, and so you can't load them from disk. But you can still make your runtime-generated images resizable!
First, I'm sure you know better than me about the deep technical issues, given you make UIKit. I also really appreciate that you are on this forum, talking about it!
That said, I have three points:
1) I would never start thinking about making a UI element by thinking about performance. I would build to my functional requirements, and then optimize if necessary.
2) I use this class that draws a button programatically, and I use it inside a UITableView, on a screen that auto-rotates. I have never had an issue, and I developed this code for early iPhones, in 2010. Can you comment on my code in particular, which uses a CAGradientLayer? I have never noticed any issues with this code in practice.
3) I never thought about generating UIImages of various colors at runtime - that does sound like a cool technique. It would be nice if stylish butons were built into UIKit - that would have saved me fumbling early on.
In the end, it is really great to have detailed knowledge of UIKit and CoreGraphics, so you save yourself time optimizing on the end, if you just know the right thing to do. And I think your comments about the accuracy of the blog's claims are righteous and a good addition. I envy your fundamental grasp of this stuff, and I try to keep learning, even as I fumble towards what I want.