UICollectionViewCell strange behaviour. Cells not updating layer correctly - ios

I have a UICollectionView with multiple cells on which I add a CAGradient layer representing the color of each cell. The problem is that when I push another view controller on top of the present view controller and then pop the second view controller, my collection view cells shift colors in a random order. To give you an idea I have attached screenshots.
This is the original order of the cells. This is correct
This happens when I push another view controller and then return
You can see that the cells shifted their colors even though I changed nothing.
This is the code I use to initialize the cells.
[collectionview reloadData] is called in -viewWillAppear so the cells load every time the view appears
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
[self filterRecords];
MyCell *cell = (MyCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"ProspectCVCell" forIndexPath:indexPath];
for (int i = 0; i < [eventsSortedForAddedDate count]; i++)
{
Events* event = (Events*)[eventsSortedForAddedDate objectAtIndex:i];
if ([event.activityLevel.activityName isEqualToString:#"None"])
continue;
color = [[event activityLevel] color];
if (![color isEqualToString:#"#FFCCCCCC"])
break;
else
color = nil;
}
if (!([color length] > 0) || color == nil)
{
color = #"#FFCCCCCC";
}
UIColor* myColor = [self getUIColorObjectFromHexString:color alpha:.9];
//cell.backgroundColor = myColor;
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = cell.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[myColor CGColor], (id)[[UIColor whiteColor] CGColor], nil];
gradient.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.85f], nil];
//[cell.layer insertSublayer:gradient atIndex:0];
[cell.layer insertSublayer:gradient atIndex:0];
cell.prospectImageView.layer.shadowColor = [UIColor blackColor].CGColor;
cell.prospectImageView.layer.shadowOffset = CGSizeMake(0, 3.0);
cell.prospectImageView.layer.shadowRadius = 3.0;
cell.prospectImageView.layer.shadowOpacity = 0.8;
cell.cellLabel.text = p.displayName;
cell.layer.borderWidth = 0.5f;
cell.layer.borderColor = [UIColor whiteColor].CGColor;
return cell;
}
There is nothing wrong in the way I get the color, I have debugged multiple times and checked that the colors I get are the correct ones.
If I do
cell.backgroundColor = myColor;
The cells do not change their colors and function as expected. So I am pretty sure that the problem lies with the CAGradientLayer.
I have tried everything that I could think of but nothing seems to work!

Try this once,
[cell.layer insertSublayer:gradient atIndex:[[cell.layer sublayers] indexOfObject:[[cell.layer sublayers] lastObject]]];
instead of
[cell.layer insertSublayer:gradient atIndex:0];
Update :
As asked in comment, Apple doc states about dequeueReusableCellWithReuseIdentifier,
Call this method from your data source object when asked to provide a new cell for the collection view. This method dequeues an existing cell if one is available or creates a new one based on the class or nib file you previously registered.
And second thing you can also remove previous layers and then can add new one at index 0. it is better because it not increase number of layer which is not necessary.

Related

tableView cell dotted border not rendering properly

Consider me novice in iOS programing. I am trying to show dotted border as the separator as well as a vertical dotted border between the fields, please check the screenshot for detailed view. Till now there is no problem, borders are rendering properly.But at the moment tableView get scrolled up or down, the position of vertical dotted lines are getting changed, please check screenshot. I know this is happening due to the cell reuse property. For all the cell dotted line creating method is same so, I am unable to find the culprit line of code. I am using ViewController and attached an UITableView to it, connected the datasource and delegates. Here is the code for dotted line creation.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
KKCustList *cell = [tableView dequeueReusableCellWithIdentifier:#"custList" forIndexPath:indexPath];
if (cell==nil)
{
cell = [[KKCustList alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"custList"];
}
/** Creating dotted lines for border **/
CAShapeLayer *shapelayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, cell.frame.size.height)];
[path addLineToPoint:CGPointMake(cell.frame.size.width, cell.frame.size.height)];
shapelayer.strokeStart = 0.0;
shapelayer.strokeColor = [UIColor firstRowColor].CGColor;
shapelayer.lineWidth = 2.0;
shapelayer.lineJoin = kCALineJoinRound;
shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer.path = path.CGPath;
[cell.layer addSublayer:shapelayer];
/** creating vertical dotted line **/
CAShapeLayer *shapelayer1 = [CAShapeLayer layer];
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(cell.soLabel.bounds.origin.x + cell.soLabel.bounds.size.width, cell.soLabel.bounds.origin.y + 10)];
[path1 addLineToPoint:CGPointMake(cell.soLabel.bounds.origin.x + cell.soLabel.bounds.size.width, cell.soLabel.bounds.size.height)];
shapelayer1.strokeStart = 0.0;
shapelayer1.strokeColor = [UIColor firstRowColor].CGColor;
shapelayer1.lineWidth = 1.0;
shapelayer1.lineJoin = kCALineJoinRound;
shapelayer1.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer1.path = path1.CGPath;
[cell.layer addSublayer:shapelayer1];
UPDATE
moved the code to awakeFromNib
#implementation KKCustList
#synthesize shapelayer1;
- (void)awakeFromNib {
[super awakeFromNib];
/** creating vertical dotted line **/
CAShapeLayer *shapelayer1 = [CAShapeLayer layer];
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(self.soLabel.bounds.origin.x + self.soLabel.bounds.size.width, self.soLabel.bounds.origin.y + 10)];
[path1 addLineToPoint:CGPointMake(self.soLabel.bounds.origin.x + self.soLabel.bounds.size.width, self.soLabel.bounds.size.height)];
shapelayer1.strokeStart = 0.0;
shapelayer1.strokeColor = [UIColor firstRowColor].CGColor;
shapelayer1.lineWidth = 1.0;
shapelayer1.lineJoin = kCALineJoinRound;
shapelayer1.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer1.path = path1.CGPath;
[self.contentView.layer addSublayer:shapelayer1];
}
Your current code has a couple of issues:
You keep adding new layers, you never remove old ones, so each time a cell is reused it'll take more memory and the lines could be in the wrong place
You never move the sublayers when the cell size changes
So you need a couple of different fixes:
Add the lines in your cell in awakeFromNib, so they're just added once in the same way that all your other views are
Override layoutSubviews in your cell and move the layers you've added each time it's called so they don't overlap other views
I'd actually drop the sublayers approach probably and use an image view for the line. If the lines might change quite a lot then I'd likely use a custom view class which has a sublayer (as per your current code) and the resizing / layout logic as described above. The reason for that is so that you can apply auto-layout constraints to the view and it will move in relation to the cell and your other views automatically.
You should also remove
if (cell==nil)
{
cell = [[KKCustList alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"custList"];
}
because it will never be used, a cell will always be created for you.

Adding subview to CollectionViewCell just once (cell reuseability issues)

I would like to add a simple gradient at the bottom of each cell. In cellForItemAtIndexPath: I have the code:
CAGradientLayer *bottomFade = [CAGradientLayer layer];
bottomFade.name = #"Gradient";
bottomFade.frame = CGRectMake(0, cell.background.frame.size.height*0.8, cell.background.frame.size.width, cell.background.frame.size.height*0.2);
bottomFade.endPoint = CGPointMake(0.5, 1.0);
bottomFade.startPoint = CGPointMake(0.5, 0.0);
bottomFade.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.0 alpha:0.0f] CGColor], (id)[[UIColor colorWithWhite:0.0 alpha:0.2f] CGColor], nil];
[cell.background.layer addSublayer:bottomFade];
The problem is that when the cell is being scrolled, the sublayer is added over and over again, which obviously is not a desired effect.
I know how to handle reuseability issues when it comes to the UITableView, but what should I do when it comes to working with the UICollectionView?
I tried to set a custom cell property isConfigured, but when I checked for it in cellForItemAtIndexPath: the result was that only two cells were generated, and then they were repeating (honestly, I have absolutely no idea why).
What would you suggest to handle such a problem? Perhaps it would be better to add some custom code in the cell's subclass?
Put your code that only requires once inside 'awakeFromNib' so it won't be called twice or thrice on its life cycle
which would appear like this:
- (void)awakeFromNib {
[super awakeFromNib];
CAGradientLayer *bottomFade = [CAGradientLayer layer];
bottomFade.name = #"Gradient";
bottomFade.frame = CGRectMake(0, self.background.frame.size.height*0.8, self.background.frame.size.width, self.background.frame.size.height*0.2);
bottomFade.endPoint = CGPointMake(0.5, 1.0);
bottomFade.startPoint = CGPointMake(0.5, 0.0);
bottomFade.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.0 alpha:0.0f] CGColor], (id)[[UIColor colorWithWhite:0.0 alpha:0.2f] CGColor], nil];
[self.background.layer addSublayer:bottomFade];
}
I've already solved the problem ;).
In the cell's subclass:
-(void)layoutSubviews {
if (!self.isConfigured) {
CAGradientLayer *bottomFade = [CAGradientLayer layer];
bottomFade.name = #"Gradient";
bottomFade.frame = CGRectMake(0, self.background.frame.size.height*0.8, self.background.frame.size.width, self.background.frame.size.height*0.2);
bottomFade.endPoint = CGPointMake(0.5, 1.0);
bottomFade.startPoint = CGPointMake(0.5, 0.0);
bottomFade.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.0 alpha:0.0f] CGColor], (id)[[UIColor colorWithWhite:0.0 alpha:0.2f] CGColor], nil];
[self.background.layer addSublayer:bottomFade];
self.isConfigured = YES;
}
}
That does the trick just fine! However, I'm still quite curious about whether there's an easy fix for the problem in cellForItemAtIndexPath: or not.
The cellForItemAtIndexPath: method will be called every time the UICollectionView instance create or reuse a UICollectionViewCell, that's where the problem is.
I don't recommend you use the property like isConfigured, the cell should keep a BOOL variable, and will check the property every time layoutSubviews been called, its a waste of memory and performance.
As nferocious76 said, awakeFromNib: method, which will be called only once on its life cycle, is the best solution for this case.
By the way, as I can't comment, you should call [super layoutSubviews] at the begin of layoutSubviews method.

UIButton with a gradient layer slow

I am customizing the UIButton with GradientLayer. I see a performance issue as view loading slowly and looks like a Jerk effect. Also when changing the orientation its same. I am using this code for an iPad application. With normal UIButton its smooth loading / rotating.
I am using the sample code found in google (GradientButton class) and is working fine. The container UIViewController which is pushed onto navigationcontroller stack to display the screen.
Here is the code:
- (void)awakeFromNib {
[self initLayers];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initLayers];
}
return self;
}
- (void)initLayers {
[self initBorder];
[self addShineLayer];
[self addHighlightLayer];
self.clipsToBounds = YES;
}
- (void)initBorder {
CALayer *layer = self.layer;
layer.cornerRadius = 8.0f;
layer.masksToBounds = YES;
layer.borderWidth = 1.0f;
layer.borderColor = [UIColor colorWithWhite:0.5f alpha:0.2f].CGColor;
}
- (void)addShineLayer {
shineLayer = [CAGradientLayer layer];
shineLayer.frame = self.layer.bounds;
shineLayer.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithWhite:1.0f alpha:0.4f].CGColor,
(id)[UIColor colorWithWhite:1.0f alpha:0.2f].CGColor,
(id)[UIColor colorWithWhite:0.75f alpha:0.2f].CGColor,
(id)[UIColor colorWithWhite:0.4f alpha:0.2f].CGColor,
(id)[UIColor colorWithWhite:1.0f alpha:0.4f].CGColor,
nil];
shineLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:0.8f],
[NSNumber numberWithFloat:1.0f],
nil];
// shineLayer.shouldRasterize = YES;
// shineLayer.rasterizationScale = [UIScreen mainScreen].scale;
[self.layer addSublayer:shineLayer];
}
#pragma mark -
#pragma mark Highlight button while touched
- (void)addHighlightLayer {
highlightLayer = [CALayer layer];
highlightLayer.backgroundColor = [UIColor colorWithRed:0.25f green:0.25f blue:0.25f alpha:0.75].CGColor;
highlightLayer.frame = self.layer.bounds;
highlightLayer.hidden = YES;
[self.layer insertSublayer:highlightLayer below:shineLayer];
}
- (void)setHighlighted:(BOOL)highlight {
highlightLayer.hidden = !highlight;
[super setHighlighted:highlight];
}
Is the performance issue due to Gradient Layer ?? Please tell me how to rectify this issue.
Try commenting out the different property assignments in your code one at a time and see if you get any performance benefit. I worked on an app once that was getting about 12FPS in core animation when I used setCornerRadius on the layer compared to nearly full frame rate when I turned that off. The issue may be your gradient, however, you won't know for sure unless you just do some a/b comparisons turning off/on the various properties.
I'll tell you right now, though, that when I've used gradient layers on buttons in table view cells, I was able to speed up the table scrolling dramatically when I just used a background image for the button instead of a gradient layer. You can try to turn on shouldRasterize, however, make sure you do that at the top level of your layer tree and not on any sublayers (although the issues that I saw with setting that parameter on all layers may have been fixed in iOS6. I just haven't tested it).

Why is cellForRowAtIndexPath adding a gradientLayer sometimes?

I have a function that looks something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// instantiate the cell
if(indexPath.row >=4 && indexPath.row <=5)
{
[CustomTableCell addGradient];
}
}
So basically, only rows 4 and 5 should have a gradient background. When the tableview first loads up, rows only rows 0 to 4 are visible and don't have gradient background which is perfect. When I scroll a little down to see rows 4 and 5, the gradient appears on them, which is great again. but when I scroll back up to the first row, all of a sudden i see a gradient on the first row!!! But i never asked row 0 to have a gradient! Additionally, when i scroll down a bit, i see gradients applied to some rows and not others. Why is this happening?
here's the addGradient function incase it's useful:
- (void) addGradient
{
if([self.layer.sublayers count] >0)
{
if([[self.layer.sublayers objectAtIndex:0] isKindOfClass:[CAGradientLayer class]])
return;
}
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[UIColor blueColor].CGColor, (id)[UIColor redColor].CGColor, nil];
[self.layer insertSublayer:gradient atIndex:0];
}
I just realized that iPhone/cocoa is actually re-using an existing cell in memory for cellForRowAtIndexpath to draw cells that appear on screen. So that's why the cell still has a gradient layer I had set before. So i updated my stylecell function to remove a layer if it exists, by using this code:
- (void) styleCell
{
if([self.layer.sublayers count] >0)
{
if([[self.layer.sublayers objectAtIndex:0] isKindOfClass:[CAGradientLayer class]])
{
[[self.layer.sublayers objectAtIndex:0] removeFromSuperlayer];
// return;
}
}
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[UIColor blueColor].CGColor, (id)[UIColor redColor].CGColor, nil];
[self.layer insertSublayer:gradient atIndex:0];
}
#end

How can I access the standard viewForHeaderInSection for a tableView?

I've got an indexed UITableView with individual sections. I would like to use a different background color for the header views in each section. I know I can completely roll my own view by implementing tableView:viewForHeaderInSection: (for example, see question # 2898361), but that seems to be "too much work" to me - the standard view looks fine, I would just have to change its background color.
But how do I access this standard view? I can't use [super tableView:viewForHeaderInSection:] because this is a question of implementing a protocol and not an issue of inheritance. Any other way I can get the standard view?
I'm almost certain you can't do this easily. I used one of my tech support request on my dev account recently asking about altering the background and borders of UITableView sections. The apple engineer told me that this really wasn't an easy thing to do, and even if you managed to do it, you would probably affect performance. He also pointed me to cocoawithlove and an article about editing uitableviews:
http://cocoawithlove.com/2009/08/adding-shadow-effects-to-uitableview.html
Really, creating your own header isn't that much effort. Below is some code I pulled out of one of my projects - it was commented out, so might not work straight away - but you can get the idea:
- (CAGradientLayer *) greyGradient {
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.startPoint = CGPointMake(0.5, 0.0);
gradient.endPoint = CGPointMake(0.5, 1.0);
UIColor *color1 = [UIColor colorWithRed:255.0f/255.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:1.0];
UIColor *color2 = [UIColor colorWithRed:240.0f/255.0f green:240.0f/255.0f blue:240.0f/255.0f alpha:1.0];
[gradient setColors:[NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil]];
return gradient;
}
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
CGFloat width = CGRectGetWidth(tableView.bounds);
CGFloat height = [self tableView:tableView heightForHeaderInSection:section];
UIView *container = [[[UIView alloc] initWithFrame:CGRectMake(0,0,width,height)] autorelease];
container.layer.borderColor = [UIColor grayColor].CGColor;
container.layer.borderWidth = 1.0f;
CAGradientLayer *gradient = [self greyGradient];
gradient.frame = container.bounds;
[container.layer addSublayer:gradient];
UILabel *headerLabel = [[[UILabel alloc] initWithFrame:CGRectMake(12,0,width,height)] autorelease];
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.font= [UIFont boldSystemFontOfSize:19.0f];
headerLabel.shadowOffset = CGSizeMake(1, 1);
headerLabel.textColor = [UIColor whiteColor];
headerLabel.shadowColor = [UIColor darkGrayColor];
NSString *title = [self tableView:tableView titleForHeaderInSection:section];
headerLabel.text = title;
return container;
}
Make sure to
#import <QuartzCore/QuartzCore.h>
By the way... this isn't supposed to mimic the look of the standard headers - its just an example. But I'm sure with a bit of trial and error you could alter this to mimic the standard ones and then change the colors slightly.
Although the other answers correctly point out you cannot access the default view to make simple modifications to it, if you have nothing to customize for a particular section header, you can return nil from tableView:viewForHeaderInSection: and the table view will use the default view.
This is helpful if you only need to customize some of your headers.
For whatever reason this is undocumented.
There is one problem with #bandejapalsa solution: the previous cell's separator is still visible with this implementation where as it is not on the default iOS sectionHeaderView. The solution I found was to use a CALayer and offset it by 1 pix. The image needs to be 1pix taller than the view frame itself.
// Create the view for the header
CGRect aFrame =CGRectMake(0, 0, tableView.contentSize.width, IMIUICustomisation.sectionHeaderViewHeight);
UIView * aView = [[UIView alloc] initWithFrame:aFrame];
aView.backgroundColor = UIColor.clearColor;
// Create a stretchable image for the background that emulates the default gradient, only in green
UIImage *viewBackgroundImage = [[UIImage imageNamed:#"greenheader.png"] stretchableImageWithLeftCapWidth:12 topCapHeight:0];
// Cannot set this image directly as the background of the cell because
// the background needs to be offset by 1pix at the top to cover the previous cell border (Alex Deplov's requirement ^_^)
CALayer *backgroungLayer = [CALayer layer];
backgroungLayer.frame = CGRectMake(0, -1, tableView.contentSize.width, IMIUICustomisation.sectionHeaderViewHeight+1);
backgroungLayer.contents = (id) viewBackgroundImage.CGImage;
backgroungLayer.masksToBounds = NO;
backgroungLayer.opacity = 0.9;
[aView.layer addSublayer:backgroungLayer];
// Take care of the section title now
UILabel *aTitle = [[UILabel alloc] initWithFrame: CGRectMake(10, 0, aView.bounds.size.width-10, aView.bounds.size.height)];
aTitle.text = [delegate tableView:tableView titleForHeaderInSection:section];
aTitle.backgroundColor = UIColor.clearColor;
aTitle.font = [UIFont boldSystemFontOfSize:18];
aTitle.textColor = UIColor.whiteColor;
// Text shadow
aTitle.layer.shadowOffset = CGSizeMake(0, 1);
aTitle.layer.shadowRadius = .2;
aTitle.layer.masksToBounds = NO;
aTitle.layer.shadowOpacity = 0.5;
aTitle.layer.shadowColor = IMIUICustomisation.selectedElementTextShadowColor.CGColor;
[aView addSubview:aTitle];
return aView;

Resources