I have an UIView with rounded corners and drop shadow, implemented and working. But the UIView has a boring background color, white. So I want to put a gradient layer as the background. Below the labels, buttons and most important, make it so the rounded corners still appears.
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = subHudView.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
[subHudView.layer addSublayer:gradient];
subHudView.layer.cornerRadius = 8;
subHudView.layer.masksToBounds = NO;
subHudView.layer.shadowOffset = CGSizeMake(-5, 5);
subHudView.layer.shadowRadius = 8;
subHudView.layer.shadowOpacity = 0.75;
This is my code as I tried to implement it, but the gradient layer is on top of everything in the view know. How can I make the gradient go under all the controls and labels? Every responding help will be appreciated.
in Swift:
let gradientLayer = CAGradientLayer()
gradientLayer.cornerRadius = 20.0
A layer added to your view (addSublayer) is drawn in front of your view's own layer, which is where all your view's drawing takes place. What you want is to draw the gradient into your view's own layer. To do so, implement +layerClass in your view, specifying that you want your view's layer to be a gradient view.
So, for example (in the view's own code):
+(Class)layerClass {
return [CAGradientLayer class];
}
-(void)awakeFromNib {
[super awakeFromNib];
CAGradientLayer* layer = (CAGradientLayer*)self.layer;
layer.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
layer.cornerRadius = 8;
layer.masksToBounds = NO;
layer.shadowOffset = CGSizeMake(-5, 5);
layer.shadowRadius = 8;
layer.shadowOpacity = 0.75;
}
If you then implement drawRect:, however, you'll of course wipe out your gradient and your rounded corners. There are ways around that...
When you addSublayer: it add the layer at the top of all the sublayers.
You should probably use something like that instead :
[subHudView.layer insertSublayer:gradient atIndex:0];
That way, the CAGradientLayer will be below everything else.
As well as the insertSublayer:atIndex: that gcamp suggested, you can also use insertSublayer:above: and insertSublayer:below: to place your layer in specific places.
[self.view.layer insertSublayer:gradient below:someUIObject];
Rather than create a new layer, override your view's layer method:
+ (Class)layerClass
{
return [CAGradientLayer class];
}
This will ensure that your view create a CAGradientLayer, rather than a basic CALayer, as the base layer.
Then, during init get hold of the layer:
CAGradientLayer *gradLayer = self.layer;
gradLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
and assign your gradients to it.
Nice and clean...
Related
I have an UITableViewController to which I successfully applied in the past a gradient background, by sending the newly added subview to back:
//performed on viewDidLoad
UIView *bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1.5*280, 1.5*SCREEN_HEIGHT)];
bgView.backgroundColor = [UIColor yellowColor];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = bgView.bounds;
gradient.startPoint = CGPointMake(0, 0);
gradient.endPoint = CGPointMake(1, 1);
UIColor *topColor = UIColorFromRGB(0x229f80);
UIColor *bottomColor = UIColorFromRGB(0x621ad9);
gradient.colors = [NSArray arrayWithObjects:(id)[topColor CGColor], (id)[bottomColor CGColor], nil];
[bgView.layer insertSublayer:gradient atIndex:0];
[self.view addSubview:bgView];
[self.view sendSubviewToBack:bgView];
bgView = nil;
However, this no longer works in iOS 11 and the bgView is actually placed on top of all the cells.
Anyone knows how I can fix this?
Or maybe I was doing it wrong all the time?
If your cells are transparent then you can try self.tableView.backgroundView = bgView;
If you don't need the background view to be scrolling together with the table view, you can use
self.tableView.backgroundView = bgView;
If you need the background view to be scrolling, change layer's zPosition to a negative value to make it work in iOS 11:
[self.view insertSubview:bgView atIndex:0];
bgView.userInteractionEnabled = NO;
bgView.layer.zPosition = -1;
Another way to fix it is to call [self.view sendSubviewToBack:bgView]; in tableView:willDisplayCell:forRowAtIndexPath:
It works for not transparent cells also.
it appears that addSubview(UIView) sendSubview(toBack: UIView) no longer works for UITableViewControllers in iOS11. So I swap to this:
// in ViewDidLoad
// set the backgroundImage
let backgroundImage = UIImageView(frame: self.view.bounds)
backgroundImage.image = UIImage(named: "background.png")
// self.view.addSubview(backgroundImage) // NO LONGER WORKS
// self.view.sendSubview(toBack: backgroundImage) // NO LONGER WORKS
self.tableView.backgroundView = backgroundImage
I need to make at 2D gradient. I have the first part of it make a gradient vertical direction from a color to a clear color:
+ (void)addFadeout:(UIView *) view withColor:(UIColor *)color {
UIColor * halfClearColor = [color colorWithAlphaComponent:0];
CALayer *layer = view.layer;
layer.masksToBounds = YES;
CAGradientLayer *shineLayer = [CAGradientLayer layer];
shineLayer.frame = layer.bounds;
shineLayer.colors = #[
(id)color.CGColor,
(id)halfClearColor.CGColor];
shineLayer.locations = #[
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f]];
[view.layer insertSublayer:shineLayer atIndex:0];
}
But I also need to be able to add multiple colors in the horizontal direction where they either fade together and is shown separately(still with the vertical gradient). I know I could just add them besides each other but that is not what I am looking for.. How would you do it?
It might be something like this but I can't seem to connect the parts:
http://cupsofcocoa.com/tag/gradient/
https://developer.apple.com/library/mac/#documentation/graphicsimaging/conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html
Edit 1:
This is what I got now:
It might be unclear what I wanted. I also want a gradient along the x-axis between multiple colors. So what I got combined with something like this: (to replace the red color)
Solution:
+ (void)addFadeout:(UIView *) view withColors:(NSArray *)colors { //CGColors
UIColor * someColor = [UIColor colorWithCGColor:((__bridge CGColorRef)[colors lastObject])];//only alpha channel used with more than one color
UIColor * halfClearColor = [someColor colorWithAlphaComponent:0];
CALayer *layer = view.layer;
layer.masksToBounds = YES;
CAGradientLayer *transparencyLayer = [CAGradientLayer layer];
transparencyLayer.frame = layer.bounds;
transparencyLayer.colors = #[
(id)someColor.CGColor,
(id)halfClearColor.CGColor];
transparencyLayer.locations = #[
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f]];
if(colors.count > 1){
CAGradientLayer * multiColoredLayer = [self getMultiColoredLayerWithColors:colors inLayer:layer];
multiColoredLayer.mask = transparencyLayer;
[view.layer insertSublayer:multiColoredLayer atIndex:0];
}
else{
[view.layer insertSublayer:transparencyLayer atIndex:0];
}
}
+ (CAGradientLayer *)getMultiColoredLayerWithColors:(NSArray *)colors inLayer:(CALayer *)layer{ //CGColors
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = layer.bounds;
gradientLayer.colors = colors;
gradientLayer.startPoint = CGPointMake(0, 0.5);
gradientLayer.endPoint = CGPointMake(1.0, 0.5);
return gradientLayer;
}
You need to use 2 of 'something' as gradients can only be drawn in a single direction at a time. You could do it with 2 CAGradientLayers, one behind the other (or one as a sublayer of the other). Or, you could do it with 2 CGGradients, both drawn into the same context.
From your comment, you want to alpha mask. The best way to apply the 'top' layer with transparency is by setting it as the mask of the 'bottom' layer:
CAGradientLayer *colorLayer = ...;
CAGradientLayer *transparencyLayer = ...;
colorLayer.mask = transparencyLayer;
In this case, any colour in the transparencyLayer is ignored and only the alpha values are used.
I'm trying to make a custom UIButton class, except, when drawing the background of the button, and adding it as a sublayer using insertSubLayer behind: method, it still appears infront of the UIButton Textlabel.
My code is posted below, Any help would be greatly appreciated.
CALayer *layer = self.layer;
layer.cornerRadius = 3.0f;
layer.masksToBounds = YES;
layer.borderWidth = 1.0f;
layer.borderColor = [UIColor colorWithWhite:0.5f alpha:0.5f].CGColor;
self.titleLabel.textColor = [UIColor greenColor];
//layer.backgroundColor = [UIColor greenColor].CGColor;
bgColor = [CAGradientLayer layer];
bgColor.frame = self.layer.bounds;
self.backgroundColor = [UIColor colorWithWhite:1 alpha:1];
bgColor.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithWhite:0.97f alpha:1].CGColor,
(id)[UIColor colorWithWhite:0.87f alpha:1].CGColor,
nil];
bgColor.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1],
nil];
[self.layer addSublayer:bgColor];
[self.layer insertSublayer:bgColor below:layer];
self.layer and layer in your code point to the same object. You're asking the layer to insert a sublayer behind itself - this is not possible. Sublayers are contained within the parent layer. Try
[self.layer insertSublayer:bgColor atIndex:0];
Instead of
[self.layer addSublayer:bgColor];
[self.layer insertSublayer:bgColor below:layer];
This will add the gradient at the lowest possible point in the layer hierarchy of your button.
I am looking to create a CAGradientLayer with rounded corners. I can't use .cornerRadius because I would like to round only the top two corners. Also, I cannot use .mask because I would like to take a screenshot of it afterwards and renderInContext does not support CALayer masks.
How can I either:
A: Create an CALayer with a gradient and two rounded corners without using masks
or
B: Take a screenshot similar to UIGraphicsGetImageFromCurrentImageContext, but respecting the mask.
I figured out a programmatic approach to doing this. It involves creating a rounded layer then drawing a square layer on top of it. You'll have to do some playing around with the gradient colors and positions to get something that looks good.
NOTE: Not sure if this solution will work for you given your requirement about masks
+ (void)makeGradientButton:(UIButton *)button {
// Create a rounded layer
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = button.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
gradient.cornerRadius = 8.0f;
// Now create a square layer that draws over the top
CAGradientLayer *gradient2 = [CAGradientLayer layer];
gradient2.frame = CGRectMake(0, 9, button.bounds.size.width, button.bounds.size.height-9);
gradient2.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor redColor] CGColor], nil];
gradient2.cornerRadius = 0.0f;
[gradient setMasksToBounds:YES];
[button.layer insertSublayer:gradient atIndex:0];
[button.layer insertSublayer:gradient2 atIndex:1];
[button setBackgroundColor:[UIColor clearColor]];
}
I tried to spruce my application up a bit by adding a CGGradientLayer to my tableview cells, and the code works great.
Only problem is that, now, whenever I select a tableview cell, it does not change colour to the selection style, whether blue or grey.
The code I am using for my gradient is
UIView *view = [[UIView alloc] initWithFrame:cell.frame];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0] CGColor], (id)[[UIColor colorWithRed:0.28 green:0.28 blue:0.28 alpha:1.0] CGColor], nil];
[cell.layer insertSublayer:gradient atIndex:0];
I do know that my cell is being selected as the text in my cells changes colour to white, which is the default selection colour.
So what I am trying to fix is the selection colour on my cells. I don't mind if I can't use the selection colours provided, but in that case can I maybe add my own transparent rect over the cell on selection?
When a UITableViewCell is selected, it inserts the selection background at index 0. That ends up being under your gradient. The view hierarchy looks like this:
<MyTableViewCell: 0xd26fb80; baseClass = UITableViewCell; frame = (0 44; 320 44); autoresize = W; layer = <CALayer: 0xd26fc90>>
| <UITableViewCellSelectedBackground: 0x6880810; frame = (0 0; 320 43); layer = <CALayer: 0x68855c0>>
| <CAGradientLayer: 0xd26ecb0> (layer)
| <UITableViewCellContentView: 0xd26fcc0; frame = (0 0; 320 43); opaque = NO; layer = <CALayer: 0xd26fd00>>
| <UIView: 0xd26e8e0; frame = (0 43; 320 1); autoresize = W+TM; layer = <CALayer: 0xd26f1a0>>
Notice that the CAGradientLayer, which I inserted at index zero when the cell was created, is after UITableViewCellSelectedBackground, so the gradient layer overlays the selected background layer visually.
To fix it, make your own subclass of UITableViewCell. Make the gradient layer a property or ivar of your subclass:
#implementation MyTableViewCell
{
CAGradientLayer *_gradient;
}
- (void)initGradient
{
_gradient = [CAGradientLayer layer];
_gradient.opaque = YES;
_gradient.frame = self.bounds;
_gradient.colors = [NSArray arrayWithObjects:(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor, nil];
[self.layer insertSublayer:_gradient atIndex:0];
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self initGradient];
}
return self;
}
- (void)awakeFromNib
{
[self initGradient];
}
Override setSelected:animated: in your subclass to show or hide the gradient layer based on whether the cell is selected:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
_gradient.hidden = selected;
}
I came upon this same problem, and since - (void)setSelected:animated: was not always called (such as when the user holds down on the cell, etc.) I found this method to work much better.
Essentially, just put your gradient in the cell.backgroundView.
UIView *cellBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = cellBackgroundView.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0] CGColor], (id)[[UIColor colorWithRed:0.28 green:0.28 blue:0.28 alpha:1.0] CGColor], nil];
[cellBackgroundView.layer addSublayer:gradient];
cell.backgroundView = cellBackgroundView;
And then set your selectedBackgroundView to however you want. this way, the cell's backgroundView is sure to be completely hidden, layers and all. This also works with subclassing, replace cell. with self..