I have a CAGradientLayer drawing in the background on viewDidLoad. It works some of the time, but then sometimes it just doesn't render anything persistently until I restart my computer. I can't figure out why some of the time it would work and then other times it won't. It will work lets say 5 builds in a row and then it will just stop rendering. No errors. Anyone have experience with this?
Background Layer Method:
+ (CAGradientLayer*) morningGradient {
UIColor *mornTop = [UIColor colorWithRed:0.843 green:0.722 blue:0.667 alpha:1.000];
UIColor *mornBottom = [UIColor colorWithRed:0.584 green:0.733 blue:0.945 alpha:1.000];
NSArray *colors = [NSArray arrayWithObjects:(id)mornTop.CGColor, mornBottom.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:0.7];
NSNumber *stopThree = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors = colors;
headerLayer.locations = locations;
return headerLayer;
}
Draw Method:
-(void)drawGrad
{
NSLog(#"drawing gradient");
CAGradientLayer *bgLayer = [BackgroundLayer morningGradient];
bgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:bgLayer atIndex:0];
}
ViewDidLoad:
- (void)viewDidLoad
{
[self drawGrad];
[super viewDidLoad];
}
From the documentation of CGGradientCreateWithColors(colorSpace, colors, locations[]) you can read
The locations array should contain the same number of items as the colors array.
I'm assuming that the same is true for CAGradientLayer but can't find anything in the documentation. I'm making this assumption because it makes sense. How would you really interpret two colors and three locations? What should be the color at the third location?
Change your code so that you pass the same number of colors as locations.
create a custom gradient view with round rect of size 3, you need to add the QuartzCore framework and then follow the code below (.h file and .m file)
#import <UIKit/UIKit.h>
#interface CustomGradientView : UIView
#end
#import "CustomGradientView.h"
#import <QuartzCore/QuartzCore.h>
#implementation CustomGradientView
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *color1=[UIColor whiteColor];
CGColorRef startColor =color1.CGColor;
UIColor *color2=[UIColor redColor];
CGColorRef endColor = color2.CGColor;
drawLinearGradient(context, rect, startColor, endColor);
CGPathRef p = [[UIBezierPath bezierPathWithRoundedRect:rect
cornerRadius:3] CGPath];
CGContextAddRect(context, rect);
CGContextAddPath(context, p);
CGContextEOClip(context);
CGContextClearRect(context, rect);
}
#end
Related
I want to give my video player a gradient effect at top and bottom of the view. I am using following way for it, but it does not give a smoother effect, i.e. it creates two white horizontal lines at both ending point of the drawing.
Actually, I have created a subclass of UIView that uses a CAGradientLayer as its layer. I have done it to make layer follow superview's orientation and it works fine. Check this answer for reference
Here is sub class of UIView called GradientView
GradientView.h
#interface GradientView : UIView
#property (nonatomic, strong, readonly) CAGradientLayer *layer;
#end
GradientView.m
#implementation GradientView
#dynamic layer;
+ (Class)layerClass {
return [CAGradientLayer class];
}
#end
Instance:
IBOutlet GradientView *viewControls;
Use it:
-(void)applyGradientEffect
{
UIColor *endColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.90];
UIColor *centerColor = [UIColor clearColor];
viewControls.layer.colors = [NSArray arrayWithObjects:
(id)[endColor CGColor],
(id)[centerColor CGColor],
(id)[centerColor CGColor],
(id)[endColor CGColor],
nil];
viewControls.layer.startPoint = CGPointMake(0.5, 1.0);
viewControls.layer.endPoint = CGPointMake(0.5, 0.0);
}
I have also tried with locations property of CAGradientLayer instead of startPoint and endPoint properties, something like following code snippet. Still it is not smoother enough.
viewControls.layer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.2],
nil];
The gradient effect is smoother only if I use only 2 colors from
bottom to top like following code. (It will create gradient effect at
bottom edge only, I need such an effect at both top and bottom)
-(void)applyGradientEffect
{
UIColor *endColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.90];
UIColor *centerColor = [UIColor clearColor];
viewControls.layer.colors = [NSArray arrayWithObjects:
(id)[endColor CGColor],
(id)[centerColor CGColor],
nil];
viewControls.layer.startPoint = CGPointMake(0.5, 1.0);
viewControls.layer.endPoint = CGPointMake(0.5, 0.0);
}
What may be the reason? Is it default behavior in this case? Am I doing anything wrong with drawing?
i want to change the color of my gradient background when i trigger an action.
I tryed by many way with no succes.
BackgroundLayer.h
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#interface BackgroundLayer : NSObject
+(CAGradientLayer*) greyGradient;
+(CAGradientLayer*) blueGradient;
#end
BackgroundLayer.m
#implementation BackgroundLayer
//Metallic grey gradient background
+ (CAGradientLayer*) greyGradient {
UIColor *colorOne = [UIColor colorWithWhite:0.9 alpha:1.0];
UIColor *colorTwo = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.85 alpha:1.0];
UIColor *colorThree = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.7 alpha:1.0];
UIColor *colorFour = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.4 alpha:1.0];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, colorThree.CGColor, colorFour.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:0.02];
NSNumber *stopThree = [NSNumber numberWithFloat:0.99];
NSNumber *stopFour = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, stopFour, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors = colors;
headerLayer.locations = locations;
return headerLayer;
}
//Blue gradient background
+ (CAGradientLayer*) blueGradient {
UIColor *colorOne = [UIColor colorWithRed:(120/255.0) green:(135/255.0) blue:(150/255.0) alpha:1.0];
UIColor *colorTwo = [UIColor colorWithRed:(57/255.0) green:(79/255.0) blue:(96/255.0) alpha:1.0];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors = colors;
headerLayer.locations = locations;
return headerLayer;
}
#end
In my view controller i have a switch on proximity (i work with ibeacon).
the switch have 4 cases. i want Case 1(Far) with blue gradient. Case 2(Near) with grey gradient. I initialise with the layer in "(void)viewWillAppear:(BOOL)animated" and then i tryed that in the did range delegate :
case CLProximityFar:
{
[[[self.view.layer sublayers] objectAtIndex:0] removeFromSuperlayer];
self.bgLayer = [BackgroundLayer blueGradient];
self.bgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:self.bgLayer atIndex:0];
....
not working.
i also tryed like this
case CLProximityNear:
{
[[[self.view.layer sublayers] objectAtIndex:0] removeFromSuperlayer];
CAGradientLayer *gradient = [[self.view.layer sublayers] firstObject];
gradient = [BackgroundLayer blueGradient];
[self.view.layer insertSublayer:gradient atIndex:0];
[self.view.layer setNeedsDisplay];
....
Not working.
Anyone can help me ? i need a solution for changing background color when i m on "near" and rechange it back when i m on "far".
Thanks in advance for your help.
I'm not using iBeacon, so in this test, I used a segmented control to switch between the layers. It should be easy enough to adapt this to your app.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CAGradientLayer *bg = [BackgroundLayer greyGradient];
bg.frame = self.view.bounds;
[self.view.layer insertSublayer:bg atIndex:0];
}
-(IBAction)changeGradient:(UISegmentedControl *)sender {
CAGradientLayer *layerToRemove;
for (CALayer *aLayer in self.view.layer.sublayers) {
if ([aLayer isKindOfClass:[CAGradientLayer class]]) {
layerToRemove = (CAGradientLayer *)aLayer;
}
}
[layerToRemove removeFromSuperlayer];
CAGradientLayer *bg;
if (sender.selectedSegmentIndex == 0) {
bg = [BackgroundLayer greyGradient];
}else{
bg = [BackgroundLayer blueGradient];
}
bg.frame = self.view.bounds;
[self.view.layer insertSublayer:bg atIndex:0];
}
There are dozens of excellent examples on here and elsewhere of how to use gradients by
-(void)drawRect:(CGRect)rect
However, I'm new to Quartz and it's likely I'm missing something. When I found this, it seems more intelligent to create a CAGradientLayer and then add it to my view with something like:
-(void)viewWillAppear
{
CAGradientLayer *bgLayer = [BackgroundLayer blueGradient];
bgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:bgLayer atIndex:0];
}
Where the blueGradient is a separate class method (within BackgroundLayer.m) which creates the gradient, as follows:
+ (CAGradientLayer*) blueGradient {
UIColor *colorOne = [UIColor colorWithRed:(120/255.0) green:(135/255.0) blue:(150/255.0) alpha:1.0];
UIColor *colorTwo = [UIColor colorWithRed:(57/255.0) green:(79/255.0) blue:(96/255.0) alpha:1.0];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors = colors;
headerLayer.locations = locations;
return headerLayer;
}
So here's my question. What if instead of this simple linear gradient, I want to create a radial one? How can I extend blueGradient so that it can handle two dimensional gradients? Or what if I want to add gradients on top of gradients? the drawRect function seems so limiting.
Or maybe that's just the wrong approach... then what's the wiring that I'm missing? How do I add gradients to my view(s) using drawRect? I want to be sure I'm doing it in a modular way so I can add gradient overlays, etc, as additional layers as necessary.
As of iOS 7, CAGradientLayer can only draw a linear gradient. It cannot draw a radial gradient.
You can either use the drawRect: approach, or you can draw your gradient into an image and display the image in a view or layer. You can draw it into a UIImage and display it in a UIImageView, or you can draw it into a CGImage and set it as the contents of a CALayer.
I want to set a gradient Background to my ImageView. I found a solution for that here on Stack Overflow. But it doesn't really satisfy my needs.
I want the gradient to be from left to right. With my the code I found I am only able to fill it from top to bottom.
I create the gradient layer with this code:
+ (CAGradientLayer *)colorGradientWithColor:(UIColor *)color
{
UIColor *colorOne = color;
UIColor *colorTwo = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:0.9];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, nil];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.colors = colors;
gradientLayer.locations = locations;
return gradientLayer;
}
Then I add the layer to my UIImageView:
CAGradientLayer *gradientLayer = [BackgroundGradient colorGradientWithColor:difficultyColor];
gradientLayer.frame = myImageView.bounds;
[myImageView.layer insertSublayer:gradientLayer atIndex:0];
And here is the result:
And here my interface setup:
As you can see, there a two UIImageViews. I want to rotate the layer in the gradient view, so it goes from left to right and not how it is now.
With the following code fragment I am able to rotate the whole ImageView. This only kind of solves my problem, but I don't want that, because it kind of destroys my interface..
myImageView.transform = CGAffineTransformMakeRotation(M_PI*1.5f);
So basically I am searching for a solution to do the gradient from left to right instead from the top to the bottom.
Do you guys have any ideas? I am new to XCode and would appreciate every tip!
This is the result I got from below code. I guess this is what you are trying to achieve
- (CAGradientLayer *)colorGradientWithColor:(UIColor *)color
{
UIColor *colorOne = color;
UIColor *colorTwo = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:0.9];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, nil];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
[gradientLayer setStartPoint:CGPointMake(0.0, 0.5)];//add these lines
[gradientLayer setEndPoint:CGPointMake(1.0, 0.5)];
gradientLayer.colors = colors;
gradientLayer.locations = locations;
return gradientLayer;
}
Try to rotate your layer before adding it to imageview,using transform.
Use startpoint and endpoint for the gradient layer:
BOOL horizontal = YES;
//(YES for left to right or NO for top to bottom)
gradientLayer.startPoint = horizontal ? CGPointMake(0, 0.5) : CGPointMake(0.5, 0);
gradientLayer.endPoint = horizontal ? CGPointMake(1, 0.5) : CGPointMake(0.5, 1);
I have a CALayer background using:
CAGradientLayer *bgLayer = [BackgroundLayer blueGradient];
bgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:bgLayer atIndex:0];
In
- (void)prepareToRotate:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
I use this line to rotate the CALayer background.
[[[self.view.layer sublayers] objectAtIndex:0] setFrame:self.view.bounds];
I am getting some tearing effects that are not pretty as the layer seemingly doesn't rotate fast enough, how can I fix this and get a seamless effect on rotate, is there a better way to resize the calayer?
Thanks,
EDIT: All my code:
the .h:
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#interface BackgroundLayer : NSObject
+(CAGradientLayer*) greyGradient;
+(CAGradientLayer*) blueGradient;
#end
the .m
#import "BackgroundLayer.h"
#implementation BackgroundLayer
//Metallic grey gradient background
+ (CAGradientLayer*) greyGradient {
UIColor *colorOne = [UIColor colorWithWhite:0.9 alpha:1.0];
UIColor *colorTwo = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.85 alpha:1.0];
UIColor *colorThree = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.7 alpha:1.0];
UIColor *colorFour = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.4 alpha:1.0];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, colorThree.CGColor, colorFour.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:0.02];
NSNumber *stopThree = [NSNumber numberWithFloat:0.99];
NSNumber *stopFour = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, stopFour, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors = colors;
headerLayer.locations = locations;
return headerLayer;
}
//Blue gradient background
+ (CAGradientLayer*) blueGradient {
UIColor *colorOne = [UIColor colorWithRed:(120/255.0) green:(135/255.0) blue:(150/255.0) alpha:1.0];
UIColor *colorTwo = [UIColor colorWithRed:(57/255.0) green:(79/255.0) blue:(96/255.0) alpha:1.0];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors = colors;
headerLayer.locations = locations;
return headerLayer;
}
#end
Prepare to rotate is simply called by
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self prepareToRotate:[[UIApplication sharedApplication] statusBarOrientation] duration:0];
}
and just contains
[[[self.view.layer sublayers] objectAtIndex:0] setFrame:self.view.bounds];
I'd recommend to use CAGradientLayer over CoreGraphics. It is much faster to render. For your issue, try to rasterize your view before the rotation.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
self.bgLayer.shouldRasterize = YES;
}
Actually, you can add a subview instead of a new layer.
#interface MyView : UIView
#end
#implementation MyView
+ (Class)layerClass
{
return [CAGradientLayer class];
}
#end
In your old inserting layer part, do following instead.
MyView *bgView = [[MyView alloc] initWithFrame:self.view.bounds];
[(CAGradientLayer *)bgView.layer setColors:colors];
bgView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.view insertSubview:bgView atIndex:0];
(You can use auto-layout if you want)
Coregraphics gradient layers are much better then CALayer. CALayer is slow and will show the gradient bands while coregraphics will have a smooth gradient. This is probably why it is happening to you.
Create a -(void)drawRect:(CGRect)rect { function and draw your gradient under there. It should take care of your issue.
There is a lot of sample code out there for this. Here is some
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 1.0 };
NSArray *colors = #[(__bridge id) startColor, (__bridge id) endColor];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);