Black Corners On Grouped UITableViewCells Only After Navigation Pops - iphone

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
(source: tinygrab.com)
   
(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.

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.

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.

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!

Related

Subclassing UITableViewCell for grouped table

I'm trying to create a a custom subclass of a UITableViewCell to be used in a grouped table.
I'm laying out the subclass with a nib. (I've just tried doing it without the nib but getting the same problem).
Whenever I've done this before I've wanted to create a whole new cell style with a different background etc... so in awakeFromNib I do this...
- (void)awakeFromNib
{
// N.B. I am not doing this, this is how I normally get rid of border
// in this case I want the border so I am not running this code.
self.backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
self.backgroundColor = [UIColor clearColor];
}
but in this case I want to keep the background with the rounded corners and I'm just adding different UI elements to it.
The cell gets created and all the elements are laid out correctly but I'm getting an annoying "second border" in the top left corner of every section.
I have a screen shot showing this.
At first I thought this was a hair on my screen or something but it isn't.
I've tried setting the backgroundView and backgroundColor but that removes the normal cell background and I want to keep them.
Any ideas how I can get rid of this?
EDIT
Just to clarify what I said above about setting backgroundColor and backgroundView.
Here is what happens when I change them...
In this I set the backgroundView to nil. The border remains but so does the little bit I'm trying to get rid of.
If I set the backgroundView to a new UIView then this happens...
I want the same background with the white background and the rounded corners border. Just not with that annoying little bit.

UIImage Not Transparent

I am trying to make a view that lets a user draw with their finger. I have a png made in Pixelmator of the brush. My drawRect: method looks like this:
- (void)drawRect:(CGRect)rect
{
NSAssert(brush != nil, #"");
for (UITouch *touch in touchesNeedingDrawing)
{
//Draw the touch
[brush drawInRect:CGRectWithPoints([touch locationInView:self], [touch previousLocationInView:self]) blendMode:0 alpha:1];
}
[touchesNeedingDrawing removeAllObjects];
}
The brush image is a png with transparency, but when I run the app, there is no transparency. Does anyone know why, and how to fix it?
Edit:
I discovered that the image is transparent, but when I call drawInRect, the image draws the transparent pixels as the background color of the view. Is there a CGBlendMode I can use to fix this?
Solution is very simple, testes on my own project. In class which you updated with custom - (void)drawRect:(CGRect)rect set in -(id)init background color for self
[self setBackgroundColor:[UIColor clearColor]];
And thats all. Tested on my project.
It seems like it could be taking the current fill color of the context.
Try setting the fill color for the context with CGContextSetFillColorWithColor() to [UIColor clearColor].CGColor
If that doesn't work, the only other solution that is simple and shouldn't have a performance hit is to have 2 views:
Background View - This will be view that has the proper background color or image
Overlay View - This view will detect the touches etc and then draw all of the brush strokes on top. The background color of this view can then be [UIColor clearColor] so when you draw the brush images, the alpha will be filled with [UIColor clearColor]. Basically the same view you have now, just with a clear background.
Note: You shouldn't need to mess with the blend mode to make this work. You should be able to use the default drawInRect: method
Is the brush png loaded to an imageView? That is, the variable brush is an object of UIImageView, isn't it?
If so, perhaps simple
brush.backgroundColor = [UIColor clearColor];
will help
i think you should try destination out blend mode: kCGBlendModeDestinationOut.
You could also draw at point instead draw in rect:
[brush drawAtPoint:[touch locationInView:self] blendMode:kCGBlendModeDestinationOut alpha:1]
A possible issue is that you are setting the blendMode to 0. I suggest using the -drawInRect: method without a blend mode. The issue may also be that your view has a black background, but that is doubtful. I would also suggest attempting to display the UIImage in a UIImageView as a test. The issue may be related to the way that PixelMator exports images.
Your problem is a fundamental misconception of how drawRect: actually works. Every time you draw something into the current graphics context, everything that was there previously will be cleared (so that only the backgroundColor remains).
Since you're only drawing the current touch(es) (touchesNeedingDrawing is emptied), there's nothing under the rectangle you're filling that could show the transparency of the image you're drawing, so the background color shows through.
You basically have two options to resolve this:
1) Keep all touches that contribute to the drawing around (don't empty the touchesNeedingDrawing array) and redraw all of them every time – this is going to be easy but quite slow.
2) Draw into a buffer of some kind (e.g. a UIImage, using UIGraphicsBeginImageContext etc.). Every time your drawing changes, create a new buffer. Draw the old buffer into the new buffer and the images for the new stroke on top of it.

Rotating UIView and still seeing the old position of the view

I am rotating a view using src.transform = CGAffineTransformMakeRotation(M_PI/2);, it is working well but I am still seeing the old position of the view (like that: http://cl.ly/233y403c2C1C451r1f28).
How can I refresh the view ? The view is in a UITableViewCell, I tried to refresh the row of the cell or even the tableview but it doesn't work.
Here is my drawRect method :
if (nil != _image) {
[_image drawInRect:rect contentMode:self.contentMode];
} else {
[_defaultImage drawInRect:rect contentMode:self.contentMode];
}
The view that I am rotating is embedded in a UIImageView which is part of the cell.
Thanks,
Martin
It sounds like you might have two copies of your image view there. When you rotate the top one, the bottom one is exposed.
What is the background colour of the UIView you're overriding drawRect in? Before drawRect is called, the background colour is applied to the context. Ensure you have a background colour set (i.e. not set to [UIColor clearColor], for example).
Also, what's the opaque property of your UIView set to?
If figured it out. I was trying to rotate the image while I was downloading it. When I rotate it from a delegate callback that is call when the image is loaded everything works fine.
All of that makes perfect sense !

Custom UITableViewCell corners distorted on orientation change (UACellBackgroundView)

I'm trying out this nice way of customizing grouped UITableViewCell backgrounds:
http://code.coneybeare.net/how-to-make-custom-drawn-gradient-backgrounds
I've implemented it in a test app and it works great, except for one thing... when rotating the device, the cell background is stretched, making the corners look distorted.
Is there any way to force a table cell to redraw itself on an orientation change? Do I need to use setNeedsDisplay somewhere?
(I've tried [cell.backgroundView setNeedsDisplay] in a couple of places just as a random guess... but that didn't work.)
Set backgroundView.contentMode = UIViewContentModeRedraw if you want it to redraw when the bounds change.
Additionally, do not override -isOpaque like the example suggests; I'm pretty sure that opaque-ness is backed by the CALayer property of the same name. Instead, set self.opaque = NO in initWithFrame:.

Change the position of the left UIImageView in a UITableViewCell in OS 3.0

I have a UITableView with standard UITableViewCells that contain an image on the left. I'm setting the image of the cell as follows:
UIImage *leftIcon = [UIImage imageNamed:#"myImage.png"];
cell.imageView.image = leftIcon;
This works fine. However, there's a difference in appearance depending on which iPhone OS the App runs. Until 2.2.1 the image is positioned about 11 pixels from the left border. In 3.0, the image is positioned directly on the left border without any space.
My goal is that the image is positioned about 11px from the left of the cell on 3.0. I tried the following without any success:
UIImage *leftIcon = [UIImage imageNamed:#"myImage.png"];
//trying to change x coordinate of the frame of the UIImageView
CGRect tmpRect = cell.imageView.frame;
tmpRect.origin.x = 10;
cell.imageView.frame = tmpRect;
cell.imageView.image = leftIcon;
I could think of two possible solutions:
1.) Implement my custom UITableViewCell with a custom UIImageView on the left.
2.) Add an 11px transparent border to myImage.png with Photoshop :)
Any recommendations?
It's a bit of a hack, but you can indent your cells from your UITableViewDelegate.
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
return 1;
}
This will have the effect of pushing over the UITableViewCell's UIImageView by about 10 points.
In my opinion, the easiest thing you can do to fix problems like this in the iPhone world is to create custom classes. Also, consider creating some functions for said classes like
-(void)redrawAtDefaultCoordinates;
-(void)redrawAtCoordinates(NSInteger x, NSInteger y);
for each custom class so that you can simply make adjustments to where they are displaying.
As to your actual problem, I would say try to get at the rectangle that the cell uses and then figure out the relative geometry that your going to need. The iPhone does a pretty good job with the guesses it makes about where you want things placed, but you always want at least something to go by if it guesses wrong.