Why UITableViewCell highlight and selection styling are such a mystery?
Let’s end up this madness once and for all.
Even being a pretty common task in the iOS programmer’s life, I repeatedly forget everything related with how to achieve UITableViewCell highlight and selection styles. So, I always have to run a quick search to refresh my memory. However, it is horrifying to see how many crappy solutions and comments one can find in a quick Google search.
I’ll start by discussing some of the clearer DON’Ts, to then move to what I think is the simplest and most elegant solution.
Mistakes, bad practices and mysteries
Custom Cells, Why?
“There’s no need for custom cells…”
Might be a little out of topic, but it astonishes me how many discussions include a comment like the former one in discussions about cell selection customization.
This is teaching bad practices to newcommers, and it likely reinforces them on many “experienced” programmers.
For them: Plausible scenarios may exist*, but if you are styling your UITableViewCell on your controller, or even on a fancy independent dataSource… you are doing something wrong at a very fundamental level. Think it through.
“Set selectionStyle to UITableViewCellSelectionStyleNone and…”
We all break the “lazymeter” sometimes… But I find particularly disturbing when we do extra work because we are being lazy to understand the interface provided by the SDK.
Sure, you can disable selection style, but what’s the benefit? None. Even more, when dealing with SDKs like this, by doing it yourself you are likely going to miss some use cases (current or future) unless you have an extraordinary knowledge of the control you are customizing.
Additionally, even if you don’t want to get the system’s default style, I think you should still customize rather than set UITableViewCellSelectionStyleNone. That setting is more like what you do when a cell selection won’t take you anywhere. In other words, it is not selectable at all**.
Long story short about UITableViewCellSelectionStyleNone: Don’t.
“just self.selectedBackgroundView = customView in setSelected:animated”
Some get closer to a solution that uses the system provided interfaces. However… Are you sure you want to instanciate, customize and assign a view EVERY time that the cell gets selected and deselected? Not really.
Based on my own early mistakes, my speculation is that by setting selectedBackgroundView on the init: or initWithFrame: methods some have failed to get functioning results. Just remember that cells instanciated by dequeueReusableCellWithIdentifier:atIndexPath: will initialize your cell with initWithStyle:reuseIdentifier:.
Documentation vs Samples
“Sets the selected state of the cell, optionally animating the transition between states. The selection affects the appearance of labels, image, and background. When the selected state of a cell is YES, it draws the background for selected cells (“Reusing Cells”) with its title in white.”
Up here, I’m quoting the official documentation for setSelected:animated:. Unlike other methods in the SDK, there in no reference to the need of calling super.
Then, WTH most code samples (including Apple’s like Lister) call it? Because… it IS necessary. You’ll get erratic behaviors otherwise.
TL:DR; The Solution
IMHO the most elegant solution is, in fact, extremely simple:
- Start by subclassing UITableViewCell to customize your cell, and leave your view controller immaculate of view styling code.
- Override initWithStyle:reuseIdentifier: to do whatever you have to do to customize your view, and…
- …at this point, be sure to also set a custom selectedBackgroundView.
(If you just need a color, paint an empty view’s background***. If you need something more complex, go for it as well :P)
- Override setSelected:animated: and setHighlighted:animated: in order to customize other views (such as UITableViewCell’s textLabel or imageView) in your cell to match your background styling.
- You are done.
- Yes! You just got a clean, simple and ever-working sell selection and highlighting styling.
What about having different background colors for selected and highlighted?
Easy! You can customize colors for each case as easily as setting selectedBackgroundView.backgroundColor = <your color> in setSelected:animated: and setHighlighted:animated:.
Ease selection styles in images
Setting images for each selection state to just change colors feels bruteforce.
I know that feeling. That’s why I stopped doing it looong ago, and you can too if you support >= iOS7. The “magic” comes from the hands of Template Images. In short, this is a a UIImage rendering mode that will ignore image colors by creating an stencil that you can paint afterwards.
Just re-instance (renderingMode is a read-only prop) your images with imageWithRenderingMode: passing UIImageRenderingModeAlwaysTemplate.
Coloring is now as simple as setting colors in UIImageView’s tintColor.
* For example, I’m pretty sure that it can be argued that using a cell factory, you might not need to subclass the cell to configure the style of certain elements.
** In the past I noticed UITableViewCellSelectionStyleNone wasn’t exclusively an styling config. By setting it to remove styling, I experienced delays on the call to the tableView delegate method tableView:didSelectRowAtIndexPath: of cells. This might be solved in more recent iOS releases, but still don’t trust it.
*** You really need to set a custom view. You can’t expect to set background to the system selectedBackgroundView, because it won’t work.