Wednesday, July 29, 2015

UITableViewCell and backgroundView


Copyright © 2015, Steven E. Houchin. All rights reserved.

I have some code that modifies a table view cell so that it acts like a gradient-colored button within a grouped table view. I'm developing for iOS 6.1. When the cell is to be shown (via tableView:willDisplayCell:forRowAtIndexPath:) the gradient color is rendered into a UIView using a CAGradientLayer object.  Actually, I create two of these UIViews: one for the normal button state and one for the pressed state.  Each of these two views are saved into the UITableViewCell's "backgroundView" and "selectedBackgroundView" properties, thus giving the button-like behavior.

But, while doing this, I ran into a problem: cell reuse. In my tableView:cellForRowAtIndexPath: callback, I was using dequeueReusableCellWithIdentifier: to get all of my table's cells - even the gradient-colored ones. One of the issues with cell reuse this way is that you must make sure to reset the reused cell's properties to a known default state.  Otherwise a cell can inherit properties set for a completely different cell last time through.

Well, I was doing just that - resetting properties - except for "backgroundView" and "selectedBackgroundView".  By not resetting those, the gradient colors appeared in cells where they were not intended.  So, I added this to my cell reset code:
[tableView setBackgroundView:nil];
[tableView setSelectedBackgroundView:nil];
Well, the result was a mess.  All of the grouped table view cells lost their rounded-edge borders.  It turns out that these background view properties have view objects set by default that provide the grouped table view cells their rounded look, so can't be set to null.  I suppose I could have copied the values and saved them somewhere to restore later when resetting the reused cell properties, but that seemed like the wrong thing to do.

The solution was to create a pool of separate reusable cells for the gradient cells. For these cells, which are within their own table view sections, I used the table view method dequeueReusableCellWithIdentifier:forIndexPath:, specifying a different identifier string for it than for the standard cells.  But, one glitch with this approach is that, to use this method, I also had to first register the cell identifier string for the class in my viewDidLoad method:
[[self tableView] registerClass:[UITableViewCell class]
               forCellReuseIdentifier:@"GradientCellId"];
Once I did that, then cell reuse in tableView:cellForRowAtIndexPath: worked great for both the gradient cells and the normal cells since they now draw from different reuse pools:
UITableViewCell *cell;
NSString *cellid;
if (MyTableSectionGradient == [indexPath section])
{
        cellid = @"GradientCellId";
        cell = [tableView dequeueReusableCellWithIdentifier:cellid
                                               forIndexPath:indexPath];
}
else
{
        cellid = @"NormalCellId";
        cell = [tableView dequeueReusableCellWithIdentifier:cellid];
}
if (!cell)
{
        cell = [[UITableViewCell alloc]
                                  initWithStyle:UITableViewCellStyleDefault
                                reuseIdentifier:cellid];
}