8

I am no graphics expert but I somehow managed to make some good looking custom grouped UITableViewCells by setting the background view to a backgroundView with some CG code. In all SDK's up to 3.1.3 (maybe 3.2... I haven't tested on the iPad) this was working great, but I think a more recent SDK has introduced a change in the way graphics are cached offscreen.

Upon first render, everything is great: The drawing is fine and the corners are transparent. If I push a couple of view controllers on the navigation stack and come back, there are now black corners that appear in the views:

BEFORE && AFTER

alt text
(source: tinygrab.com)
    alt text
(source: tinygrab.com)

I have tons of code, most of which is written up here. I have tried tweaking to the best of my ability, looking at the docs for applicable changes, but after at least 8 hours in I still cannot find what might cause this. I have tried setting every view I can think of to be backgroundColor=clearColor and opaque=NO What else am I missing? Any debugging tips?

UPDATE:

I have some debug code in viewDidAppear that prints the backgroundColor and class description of all the subviews.

- (void)debugView:(UIView *)view {
    DebugLog(@"%@ - %@", view.backgroundColor, [[view class] description]);
    for (UIView* child in view.subviews) {
        [self debugView:child];
    }
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [DownloadController.networkQueue setSuspended:NO];
    for (TTTableViewCell *cell in [self.tableView visibleCells]) {
        [cell debugView:cell];
    }
}

With this code, I inspect the backgroundColor settings of the cell views on first load, when it is fine, and then again after coming back. There are some differences, but all the colors are still set to be clear. This leads me to believe the issue is underneath the UITableViewCell.

UPDATE 2:

I have created a simple sample application to highlight the problem.

Community
  • 1
  • 1
coneybeare
  • 33,113
  • 21
  • 131
  • 183
  • That is weird, off the top of my head I would say maybe when cells get reused something is happening get rid of the cell reuse code and see if that helps. Just a thought. – nickthedude May 11 '10 at 16:06
  • Good idea. Unfortunately, not using a reuse identifier yields the same results – coneybeare May 11 '10 at 16:11
  • I can confirm that the corners appear in 3.2 but not in 3.1.x. Your sample project had the same behavior in my environment. Of note, the black corners still appeared when I switched the table to display as a plain table. Sorry I can't help more. :( – MrHen May 13 '10 at 16:35
  • Where is the black coming from? If you set the backgroundColor in the view controller to red, would you see red instead? Also, the dumpWindows() function might be helpful, see: http://stackoverflow.com/questions/2715534/where-does-a-uialertview-live-while-not-dismissed/2715772#2715772 – progrmr May 13 '10 at 17:59
  • The black is not coming from anywhere in my code it seems. I have tried changing all colors on the entire app to clear. In the sample app, the background color of the view is blue, and the background color of the tableview is red. I checked out the dumpWindows() and the output is here: http://gist.github.com/400190 – coneybeare May 13 '10 at 18:23

3 Answers3

5

I have tested the sample application and can reproduce the black corners issue.

After some experiments, it seems that the black corners issue is related to the caching of the layers used to render the table view. The geometry of the cell's layer seems to be important:

  • On the first paint, the cell is asked to be painted into a rect. Your code is painting a rounded path, but clipped out the corners. As the underlying tableview is already drawn, no problem occurs. The rect zone is cached, with its corners unpainted.
  • When the controller is pushed, a cached image is stored, with rectangular placeholders for the cells.
  • When the controller is popped, the cached image and the cells are drawn. But the place to draw cells is rectangular but the cell's cached image is not, leading to black corners.

In order to get rid of the black corners you can:

  • Make sure that all the cell's rect is painted. This means using the same color to file the cell before drawing the edge as the tableview's background color. If your tableview use the default background color, you can use [UIColor groupTableViewBackgroundColor].CGColor as filling color; it is a pattern based color and follows device orientation (yeah); but the painting is not perfectly aligned with the background (damn).
  • Use a CALayer mask on the cell's layer. This implies creating a mask CGImage, set it as the layer's content and assign the mask layer to the cell's layer. Not sure about the performance.

Hope it helps a bit.

Update

After some unsuccessful attempts, I dropped the mask idea because it was too clumsy.

I have re-read the code for the cell's layer and found out a way to remove the black corners in a simple way. The basic idea is that a CAGradientLayer is fully transparent only if its gradient colors are clear. By using the following display method, the black corners went away (both on the simulator and on the device):

- (void)display {
    if (_override) {
        self.colors =
        [NSArray arrayWithObjects:
         (id)[UIColor colorWithRed:colorComponents[0] green:colorComponents[1] blue:colorComponents[2] alpha:colorComponents[3]].CGColor,
         (id)[UIColor colorWithRed:colorComponents[4] green:colorComponents[5] blue:colorComponents[6] alpha:colorComponents[7]].CGColor,
         nil];
    } else {
        self.colors =
        [NSArray arrayWithObjects:
         (id)[UIColor clearColor].CGColor,
         (id)[UIColor clearColor].CGColor,
         nil];
    }
    [super display];
}

Of course, this could be optimized a bit:

  • Create the color arrays once.
  • Provide a custom setter for override property that change the layer's colors.
  • Remove the display method as it is no needed anymore.
Laurent Etiemble
  • 27,111
  • 5
  • 56
  • 81
  • I agree it is a problem with the entire rect not being painted. If you leave out the gradient layer the black is gone too. – progrmr May 14 '10 at 18:58
  • Just a quick question: Why did you need a CAGradientLayer subclass for the cell if you draw the gradient yourself in the cell ? – Laurent Etiemble May 16 '10 at 16:50
  • For plain table styles (pure rectangle) I use a CAGradientLayer to draw the cells much faster. The drawing code is called conditionally in the GradientLayer subclass in the display method. If it is a plain table cell, I use self.colors, otherwise I call [super display] Most of my apps are using plain table view styles, but I share the background view class with the gradient cells – coneybeare May 17 '10 at 03:40
  • Although they both work, this solution is better than progrmr's solution because there are no unnecessary and expensive rounding calculations for the corner radii. Congrats on your bounty! – coneybeare May 18 '10 at 17:01
2

I experimented with your sample code using OS3.2 on the simulator and it definitely shows the same symptom. I tried a few things and ended up with this fix:

self.cornerRadius = 12;

Add this into UAGradientCellBackgroundLayer, in init method.

progrmr
  • 75,956
  • 16
  • 112
  • 147
  • Thanks for looking into it. The cells render correctly the first time, but only on the return do they get messed up. In this return, the cell drawing code is not called at all, but is pulled from some internal cache – coneybeare May 13 '10 at 23:12
  • After this fix they render properly even after returning. – progrmr May 14 '10 at 04:10
  • This does indeed fix the cell rendering issue. I am going to hold off on marking it as answered until I see if anybody else has some solutions that don't really degrade the scrolling performance. Using .cornerradius seems to be very expensive and uncached when reusing cells – coneybeare May 14 '10 at 13:48
  • Look at this screenshot of the fixed problem: http://cl.ly/2b4202dc8a5c169c0997 If you zoom in and inspect one of the middle (non-rounded) cells, you can see the black background poking through on the edges of the cells. You can also see where the corner radius is removing this black edge. Don't get me wrong, this solution is the best so far and I am very thankful for your hard work in coming up with a solution, but it still feels "dirty" – coneybeare May 14 '10 at 15:43
  • Yeah I see that now. I think the real answer lies in filling in all of the rect when you create the background. – progrmr May 14 '10 at 19:13
  • I was hoping to avoid doing that because I have a patterned background and the patterns don't line up – coneybeare May 14 '10 at 21:43
  • I was able to make it better and scroll a little better by conditionally making the corner radius 12 in updateLayer AND by make the rect not subtract 1/2 pixel from each side. I make the cornerradius 0 on the middle cells and 10.5 on the top, bottom and single cells. If there is no better solution for scrolling, this is the one I will go with. Thanks. – coneybeare May 15 '10 at 14:27
  • Was there a significant performance penalty in using cornerRadius? I never noticed any but I didn't measure. – progrmr May 16 '10 at 17:49
0

Throwing this out there (perhaps it will help), adding the following to cellForRowAtIndexPath has an interesting effect:

cell.backgroundView.alpha = 0.4;
cell.selectedBackgroundView.alpha = 0.4;

Not all of the cells have the black background. Changing the alpha seems to change the probability that any given cell will improperly render. alpha = 1.0 guarantees a black background, whereas any lower decreases the probability of this effect.

The black background also fades, so I'm sure you all knew, but yeah, it has to do with the UAGradientCellBackgroundView.

Good luck!

Daniel Amitay
  • 6,677
  • 7
  • 36
  • 43