Gradient Layer over ImageView reuse - ios

Within my 'cellForRowAtIndexPath' I have the following code to populate the image (async) and apply a custom gradient
This worked fine, until I added a custom colour per cell. What it's currently doing is recycling the previous colour rather than applying a new one - this is presumably due to the following line which will skip the gradient code once applied to each cell:
if(!cell.gradientMask){
However, if I comment this out, the colours work but the gradient on each cell will stack up as a new layer is added each time (see existing issue)
I presume I need to remove the gradientLayer on each iteration, is that the best approach or perhaps I need to subclass the UIImageView?
if (!cell.gradientMask) {
gradientMask = [CAGradientLayer layer];
gradientMask.frame = cell.eventImage.layer.bounds;
gradientMask.startPoint = CGPointMake(0.5, 0.2);
gradientMask.endPoint = CGPointMake(0.5, 1.0);
/* THIS COLOUR CHANGES FOR EACH CELL */
gradientMask.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.0f] CGColor],
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f] CGColor],nil];
[cell.eventImage.layer insertSublayer:gradientMask atIndex:0];
cell.gradientMask = gradientMask;
}

You need to set the colors outside the block which creates the gradientMask:
if (!cell.gradientMask) { //Operations that need to be carried out only ONCE are put inside this block
gradientMask = [CAGradientLayer layer];
gradientMask.frame = cell.eventImage.layer.bounds;
gradientMask.startPoint = CGPointMake(0.5, 0.2);
gradientMask.endPoint = CGPointMake(0.5, 1.0);
[cell.eventImage.layer insertSublayer:gradientMask atIndex:0];
cell.gradientMask = gradientMask;
}
//Operations that need to be carried out again and again are outside the block
if (condition1)
{
cell.gradientMask.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.0f] CGColor],
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f] CGColor],nil];
}
else
{
cell.gradientMask.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:0.0f] CGColor],
(id)[[UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f] CGColor],nil];
}

Related

Adding CAGradient layer but unable to get Gradient effect

Here i am using this code to give a gradient effect but not getting the effect at all.
CAGradientLayer *layer2 = [CAGradientLayer layer];
NSArray *gradientColors = [NSArray arrayWithObjects:(id)[self colorWithHexString:#"3eb79d"].CGColor,(id)[self colorWithHexString:#"3fb65c"].CGColor,(id)[self colorWithHexString:#"3cb7da"].CGColor, nil];
[layer2 setColors:gradientColors];
[layer2 setFrame:cell.userBackgroundView.layer.frame];
[cell.userBackgroundView.layer insertSublayer:layer2 atIndex:0];
cell.userBackgroundView.clipsToBounds = YES;
Try this
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame =cell.userBackgroundView.frame;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor clearColor] CGColor], (id)[[UIColor blackColor] CGColor], nil];
[cell.userBackgroundView.layer insertSublayer:gradient atIndex:0];
Try to change index of sublayer from 0 to -1
[cell.userBackgroundView.layer insertSublayer:layer2 atIndex:-1];
Make sure your userBackgroundView is not covered by another view.
Use
[layer2 setFrame:cell.userBackgroundView.bounds];
instead of
[layer2 setFrame:cell.userBackgroundView.layer.frame];
If you are using autoLayout, make sure to set the frame in viewDidAppear or viewDidLayOutSubviews

Gradient Mask is applied multiple times in reused prototype cell

I have a tableview, with a customer UITableViewCell class - this is created as a prototype cell in Storyboard/Builder.
Because my cell is linked to the Storyboard prototype, I reference it as follows (cellIdentifier matches the ID on the prototype cell):
EventsListTableViewCell *cell = (EventsListTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
As such, the cell is always initialised and ready (I can't use "if (cell == nil{...} ")
This is fine, however I want to add a gradient layer to my cell, which I'm doing within cellForRowAtIndex:
gradientMask = [CAGradientLayer layer];
gradientMask.frame = cell.eventImage.layer.bounds;
gradientMask.startPoint = CGPointMake(0.5, 0.2);
gradientMask.endPoint = CGPointMake(0.5, 1.0);
gradientMask.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.0f] CGColor],
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f] CGColor],nil];
[cell.eventImage.layer insertSublayer:gradientMask atIndex:0];
The issue here, is that the gradientMask gets applied for each re-use of the cell, so when I scroll down it gets darker and darker
I realise I need to only apply this gradientMask once, when the cell is first created, however I'm not sure where to call this code, as I never 'init' the cell (this is handled by the storyboard)
I do have a custom class for this cell, but it only contains properties and no methods?
There are various ways of achieving this:
1- Property in UITableViewCell subclass
Create a property in the EventsListTableViewCell class which will hold a reference to the gradientMask:
#interface EventsListTableViewCell : UITableViewCell
#property (weak, nonatomic) CAGradientLayer *gradientMask;
#end
And then in the cellForRowAtIndexPath: method:
if (!cell.gradientMask) {
gradientMask = [CAGradientLayer layer];
gradientMask.frame = cell.eventImage.layer.bounds;
gradientMask.startPoint = CGPointMake(0.5, 0.2);
gradientMask.endPoint = CGPointMake(0.5, 1.0);
gradientMask.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.0f] CGColor],
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f] CGColor],nil];
[cell.eventImage.layer insertSublayer:gradientMask atIndex:0];
cell.gradientMask = gradientMask;
}
This will make sure that the gradientMask is initialized only once.
2- name property of CALayer
This way, you don't need to create a property and everything can be handled in the cellForRowAtIndexPath: method itself.
BOOL gradientFound = NO;
for (CALayer *layer in cell.eventImage.layer.sublayers)
{
if ([layer.name isEqualToString:#"gradientLayer"])
{
gradientFound = YES;
break;
}
}
if (!gradientFound)
{
gradientMask = [CAGradientLayer layer];
gradientMask.frame = cell.eventImage.layer.bounds;
gradientMask.name = #"gradientLayer"; //Set the name
gradientMask.startPoint = CGPointMake(0.5, 0.2);
gradientMask.endPoint = CGPointMake(0.5, 1.0);
gradientMask.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.0f] CGColor],
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f] CGColor],nil];
[cell.eventImage.layer insertSublayer:gradientMask atIndex:0];
}
3- Declaring the gradienLayer in the UITableViewCell subclass itself
This is the cleanest way as it also isolates the code related to the cell within it's class. You can initialize the cell in the awakeFromNib method.
#implementation EventsListTableViewCell
{
CAGradientLayer *gradientMask;
}
-(void)awakeFromNib
{
gradientMask = [CAGradientLayer layer];
gradientMask.frame = cell.eventImage.layer.bounds;
gradientMask.startPoint = CGPointMake(0.5, 0.2);
gradientMask.endPoint = CGPointMake(0.5, 1.0);
gradientMask.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.0f] CGColor],
(id)[[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f] CGColor],nil];
[self.eventImage.layer insertSublayer:gradientMask atIndex:0];
}
#end
Since you are adding this layer yourself out of storyboard file, this layer will net be deleted during the cellReuse cycle. You have to do it manually.
I usually create a subclass for the cell and override prepareForReuse method, where I remove all the custom views and layers I have added there. This way everything will work smoothly.
And don't forget to add your subviews to cell's contentView property only.

Circular gradient color "Not linear" in UIView

I have an issue with the gradient color, I'm not able to make the gradient color appear as circular, it always appear like linear, i need the exact gradient in the following image:
PS: this is the code i use:
UIView *view = [[UIView alloc] initWithFrame:self.view.frame];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.startPoint = CGPointMake(0.0,0.4);
gradient.endPoint = CGPointMake(0.0,0.6);
view.layer.masksToBounds = YES;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithRed:153.0/255.0 green:204.0/255.0 blue:0.0/255.0 alpha:1.0] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
[view.layer insertSublayer:gradient atIndex:0];
[self.view addSubview:view];
There is a CGContextDrawRadialGradient in CGContext. I hope it will help you

UIPickerView - gradient broken in iOS 8

Before iOS 8, able to set a gradient background to a UIPickerView so the spinner would show on top and behind it, a gradient background as shown here:
Here is the code for the above prior to iOS 8:
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = pickerView.bounds;
gradient.colors = [NSArray arrayWithObjects:
(id)[[UIColor blackColor] CGColor],
(id)[[UIColor darkGrayColor] CGColor],
(id)[[UIColor lightGrayColor] CGColor],
(id)[[UIColor whiteColor] CGColor],
(id)[[UIColor lightGrayColor] CGColor],
(id)[[UIColor darkGrayColor] CGColor],
(id)[[UIColor blackColor] CGColor],
nil];
[pickerView.layer insertSublayer:gradient atIndex:0];
Using the above code in iOS 8, here is what displays:
Both pickers still function, but as you can see, the UI is not acceptable.
Suggestions? Thanks in advance!
Okay, I found a work-around, but still open to other, better options. My solution is:
Make UIPickerView backgroundColor transparent
Place a UILabel behind the UIPickerView
Create my gradient as above and add it to the UILabel as a subLayer :atIndex 0
Here is the code sample:
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = colorPickerBackgroundLabel.bounds;
gradient.colors = [NSArray arrayWithObjects:
(id)[[UIColor blackColor] CGColor],
(id)[[UIColor darkGrayColor] CGColor],
(id)[[UIColor lightGrayColor] CGColor],
(id)[[UIColor whiteColor] CGColor],
(id)[[UIColor lightGrayColor] CGColor],
(id)[[UIColor darkGrayColor] CGColor],
(id)[[UIColor blackColor] CGColor],
nil];
[colorPickerBackgroundLabel.layer addSublayer:gradient];
Here is the finished screen area capture:

iOS UITableViewCell CornerRadius Constant

I am extending UITableView and overriding setBackgroundView to add a gradient to cell in a grouped style table view. All good so far...
In order to apply rounded corners to the cell, I set the cornerRadius to 10 which looks about right.
All is working well but I don't like the idea of hardcoding the cornerRadius. Is this constant somewhere in the Cocoa code base?
Example:
- (void) setBackgroundView:(UIView *)backgroundView{
NSLog(#"called setBackground");
NSLog(#"%f", backgroundView.layer.shadowRadius); //yields 0.00000
NSLog(#"%f", backgroundView.layer.cornerRadius); //yields 3.00000
CAGradientLayer *gradient = [CAGradientLayer layer];
[gradient setCornerRadius:10];
[gradient setMasksToBounds:YES];
[gradient setBorderWidth:0.8f];
[gradient setBorderColor:[[UIColor darkGrayColor] CGColor]];
gradient.frame = backgroundView.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor greenColor] CGColor], (id) [[UIColor colorWithRed:.05 green:.65 blue:.05 alpha:1] CGColor],(id)[[UIColor colorWithRed:.05 green:.65 blue:.05 alpha:1] CGColor], (id)[[UIColor colorWithRed:.05 green:.45 blue:.05 alpha:1] CGColor], nil];
gradient.locations = [NSArray arrayWithObjects: (id)[NSNumber numberWithFloat:0.10], (id)[NSNumber numberWithFloat:0.50], (id)[NSNumber numberWithFloat:0.50], (id)[NSNumber numberWithFloat:1.0], nil];
[backgroundView.layer insertSublayer:gradient atIndex:0];
[super setBackgroundView:backgroundView]; }

Resources