adding gradient to UIView in drawRect - objective-c

I am trying to add a CAGradientLayer in my drawRect, the code is the following:
- (void)drawRect:(CGRect)rect {
CAGradientLayer *gradientOverlay = [CAGradientLayer layer];
CGColorRef grayColor = [UIColor colorWithRed:37/255.f green:37/255.f
blue:37/255.f alpha:1.0].CGColor;
CGColorRef blueColor = [UIColor colorWithRed:23.0/255.0 green:171.0/255.0
blue:219.0/255.0 alpha:1.0].CGColor;
gradientOverlay.colors = [NSArray arrayWithObjects:
(id) grayColor,
(id) grayColor,
(id) blueColor,
nil];
gradientOverlay.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0.4],
[NSNumber numberWithFloat:1],
nil];
CGPoint startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect));
gradientOverlay.startPoint = startPoint;
gradientOverlay.frame = self.bounds;
gradientOverlay.endPoint = endPoint;
self.layer.mask = gradientOverlay;
}
Any idea why this is not working?

How about trying this instead of overriding drawRect?
+ (void) applyGradient:(NSArray *)cgColors toView:(UIView *)view {
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = cgColors;
for(CALayer *layer in view.layer.sublayers) {
if([layer isKindOfClass:[CAGradientLayer class]]) return;
}
[view.layer insertSublayer:gradient atIndex:0];
}

Add your CAGradientLayer as a sublayer of your self.layer.
Note: If you're doing it in the drawRect: method make sure you're not adding a new CAGradientLayer every time drawRect: is called.
UPDATE (regarding the comment):
Here's the code for what you're asking:
//Create a layer that holds your background image and add it as sublayer of your self.layer
CALayer *layer = [CALayer layer];
layer.frame = self.layer.frame;
layer.contents = (id)[UIImage imageNamed:#"background.png"].CGImage;
[self.layer addSublayer:layer];
//Create your CAGradientLayer
CAGradientLayer *gradientOverlay = [CAGradientLayer layer];
CGColorRef grayColor = [UIColor colorWithRed:37/255.f green:37/255.f
blue:37/255.f alpha:1.0].CGColor;
CGColorRef blueColor = [UIColor colorWithRed:23.0/255.0 green:171.0/255.0
blue:219.0/255.0 alpha:1.0].CGColor;
gradientOverlay.colors = [NSArray arrayWithObjects:
(id) grayColor,
(id) grayColor,
(id) blueColor,
nil];
gradientOverlay.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0.4],
[NSNumber numberWithFloat:1],
nil];
CGPoint startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect));
gradientOverlay.startPoint = startPoint;
gradientOverlay.frame = self.layer.frame;
gradientOverlay.endPoint = endPoint;
//set its opacity from 0 ~ 1
gradientOverlay.opacity = 0.6f;
//add it as sublayer of self.layer (it will be over the layer with the background image
[self.layer addSublayer:gradientOverlay];
Note: You don't have to do this in drawRect: method. Create your layer hierarchy in the init method for example, if you need to change the geometry of some views/layers override the layoutSubviews method.

Generally, you should only override drawRect: when you will be drawing your view using CoreGraphics.
A better place to put your code would be in init if you're creating this view in code, or awakeFromNib if you're placing this view in a .xib

Related

Making a gradient arc?

i wanna make an arc with the use of CAGradientLayer in such a way:-
20% of the arc have same single color then other 80% of arc has a gradient of two colors.
I already tried by hand on locations
startPoint endPoint property of CAGradientLayer but couldn't get the success and already go through from tutorials but somehow couldn't understand the concept properly.please solve my problem with clear description of it.
// for beizier path
- (UIBezierPath *)samplePath
{
UIBezierPath *path = [UIBezierPath bezierPath];
path = [UIBezierPath bezierPath];
[path addArcWithCenter:CGPointMake(200, 200) radius:30.0f startAngle:(3*M_PI)/4 endAngle:M_PI/4 clockwise:YES];
path.lineWidth = 30;
[[UIColor redColor] setStroke];
// [[UIColor colorWithRed:arc4random() green:arc4random() blue:arc4random() alpha:1.0] setFill];
[path stroke];
return path;
}
- (void)startAnimation
{
CAShapeLayer *shapeLayer;
if (self.pathLayer == nil)
{
shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [[self samplePath] CGPath];
shapeLayer.strokeColor = [[UIColor redColor] CGColor];
shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 30;
self.pathLayer = shapeLayer;
}
[self animationBasic];
[self gradientLayer:shapeLayer];
}
-(void)animationBasic
{
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 3.0;
pathAnimation.fromValue = #(0.0f);
pathAnimation.toValue = #(1.0f);
[self.pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
}
-(void)gradientLayer:(CAShapeLayer *)shapelayer
{
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.frame;
gradientLayer.colors = #[(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor redColor].CGColor ];
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:1.0f],
nil];
// gradientLayer.startPoint = CGPointMake(0,1);
// gradientLayer.endPoint = CGPointMake(1,1);
[self.layer addSublayer:gradientLayer];
gradientLayer.mask = shapelayer;
}
The locations property of a CAGradientLayer instance should be populated with the starting points (I know, the word stops makes it sound the opposite) of each color as specified in the colors property, from 0 to 1. These stops are rendered vertically from top to bottom.
So, this should work:
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.5f],
nil];
Now, at the same time, if you want to make the gradient horizontal (or any other direction), you can make use of the startPoint and endPoint properties.
gradientLayer.startPoint = CGPointMake(0.0, 0.5); //Default is (0.5, 0,0)
gradientLayer.endPoint = CGPointMake(1.0, 0.5); //Default is (0.5, 1.0)
//The gradientLayer will be rendered horizontally.
The startingPoint and endingPoint properties determine the direction and layout of the gradient in the layer. These are defined in the unit coordinate system, where each point is defined as a set of unit coordinates and the top left corner being the origin. So, (0,0) corresponds to the origin, (1,1) corresponds to the bottom right corner. You can check this excellent answer for a better explanation.
So to summarize, the gradient is rendered in the direction and region as specified by the startPoint and endPoint properties, with the colors rendered in the ratio specified in the locations property.
At the end, you can only understand this fully by tinkering with the properties and using various combinations to see what effect is rendered.

Collection element of type 'void *' is not an Objective-C object

Im trying to add the a gradient view to my background on for an iOS app.
Using the pod UIColor+uiGradients I am adding a GradientLayer to my background under viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
UIColor *startColor = [UIColor uig_emeraldWaterStartColor];
UIColor *endColor = [UIColor uig_emeraldWaterEndColor];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.view.bounds;
gradient.startPoint = CGPointMake(0, 0);
gradient.endPoint = CGPointMake(self.view.frame.size.width, 0);
gradient.colors = #[(id)[startColor CGColor], (id)[endColor CGColor], nil];
[self.view.layer insertSublayer:gradient atIndex:0];
}
The error occurs on line gradients.colors = #[(id)[startColor CGColor], (id)[endColor CGColor], nil];
error :
Collection element of type 'void *' is not an Objective-C object.
Thanks for any assistance.
Remove the nil from the end of the array. You can't put nil in an Objective-C array, nor is it necessary in #[] literals. So:
gradient.colors = #[(id)[startColor CGColor], (id)[endColor CGColor]];

Animate Mask in Objective C

I am making an application where I have to show 3 images in a single screen, and when User touch one image then that Image should be shown by Animating and resizing.
So for 1st Step I did Masking of 3 images with 3 different Transparent Mask Image from
How to Mask an UIImageView
And my method is like below
- (UIImageView*) maskedImage:(UIImage *)image withMasked:(UIImage *)maskImage {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask1 = [CALayer layer];
mask1.contents = (id)[maskImage CGImage];
mask1.frame = CGRectMake(0, 0, 1024, 768);
imageView.layer.mask = mask1;
imageView.layer.masksToBounds = YES;
return imageView;
}
And I am calling this as
self.image2 = [UIImage imageNamed:#"screen2Full.png"];
self.mask2 = [UIImage imageNamed:#"maskImage.png"];
self.imageView2 = [self maskedImage:self.image2 withMasked:self.mask2];
[self.completeSingleView addSubview:self.imageView2];
This is working perfectly.
Now for Step 2. Animate the Mask, so that Full Image can be shown. I have googled a lot about this but didn't get success. So Please help me. I just want to know how can we Animate and Resize the Mask Image So that I can view a full Image. My concept is as below.
So finally i got the solution from multiple sites and some own logic and hard work obviously ;). So here is the solution of this
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
animation.toValue = [NSValue valueWithCGPoint:point];
animation.duration = .3;
layer.position = point;
[layer addAnimation:animation forKey:#"position"];
}
-(void)resizeLayer:(CALayer*)layer to:(CGSize)size
{
CGRect oldBounds = layer.bounds;
CGRect newBounds = oldBounds;
newBounds.size = size;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithCGRect:oldBounds];
animation.toValue = [NSValue valueWithCGRect:newBounds];
animation.duration = .3;
layer.bounds = newBounds;
[layer addAnimation:animation forKey:#"bounds"];
}
- (UIImageView*) animateMaskImage:(UIImage *)image withMask:(UIImage *)maskImage width:(int )wd andHeight:(int )ht andSize:(CGSize)size andPosition:(CGPoint)pt {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask = [CALayer layer];
mask.contents = (id)[maskImage CGImage];
mask.frame = CGRectMake(0, 0, wd, ht);
//This needs to be place before masking. I don't knwo why. But its working :)
[self resizeLayer:mask to:size];
[self moveLayer:mask to:pt];
imageView.layer.mask = mask;
imageView.layer.masksToBounds = YES;
return imageView;
}
You just need to call this as
[self.imageView1 removeFromSuperview];
self.imageView1 = [self animateMaskedImage:self.image1 withMasked:self.mask1 width:1024 andHeight:768 andSize:CGSizeMake(1024*4, 768*4) andPosition:CGPointMake(1024*2, 768*2)];
[self.completeSingleView addSubview:self.imageView1];

Objective C - Faded Gradient on left/right sides of UICollectionView

I have a horizontal-scrolling UICollectionView within the main view of a view controller like so (Grey is UIView, Wood is UICollectionView):
I want to add fixed faded gradients on the far left & far right sies of this UICollectionView so that the scrolls appear to vanish as the user scrolls. How would I go about doing this? Does it involve some use of CAGradientLayer? I would be grateful for any help you can give me!
I actually managed to figure this out using one mask layer thanks to this tutorial at cocoanetics. Here's what I did:
#interface ScalesViewController : UIViewController
{
CAGradientLayer *maskLayer;
}
#end
Then in the .m, I placed in the following:
- (void)viewDidAppear:(BOOL)animated
{
[super viewWillAppear: animated];
if (!maskLayer)
{
maskLayer = [CAGradientLayer layer];
CGColorRef outerColor = [[UIColor colorWithWhite:0.0 alpha:1.0] CGColor];
CGColorRef innerColor = [[UIColor colorWithWhite:0.0 alpha:0.0] CGColor];
maskLayer.colors = [NSArray arrayWithObjects:
(__bridge id)outerColor,
(__bridge id)innerColor,
(__bridge id)innerColor,
(__bridge id)outerColor, nil];
maskLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.125],
[NSNumber numberWithFloat:0.875],
[NSNumber numberWithFloat:1.0], nil];
[maskLayer setStartPoint:CGPointMake(0, 0.5)];
[maskLayer setEndPoint:CGPointMake(1, 0.5)];
maskLayer.bounds = self.mainCollectionView.bounds;
maskLayer.anchorPoint = CGPointZero;
[self.mainCollectionView.layer insertSublayer: maskLayer atIndex: 0];
}
}
This creates a nice "fade to black" effect on both sides of my collection view. More colors can be added to the locations & color properties to refine the gradient blend. The start/endpoints determine the direction and location of the gradient.
Try to add two layers of CAGradientLayer on the collection view sublayer:
#import <QuartzCore/QuartzCore.h>
CAGradientLayer *leftShadow = [CAGradientLayer layer];
leftShadow.frame = CGRectMake(0, 0, 100, self.collectionView.frame.size.height);
leftShadow.startPoint = CGPointMake(0, 0.5);
leftShadow.endPoint = CGPointMake(1.0, 0.5);
leftShadow.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.0 alpha:0.4f] CGColor], (id)[[UIColor clearColor] CGColor], nil];
[self.collectionView.layer addSublayer:leftShadow];
CAGradientLayer *rightShadow = [CAGradientLayer layer];
rightShadow.frame = CGRectMake(CGRectGetWidth(self.collectionView.frame)-100.0, 0, 100, self.collectionView.frame.size.height);
rightShadow.startPoint = CGPointMake(1.0, 0.5);
rightShadow.endPoint = CGPointMake(0, 0.5);
rightShadow.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.0 alpha:0.4f] CGColor], (id)[[UIColor clearColor] CGColor], nil];
[self.collectionView.layer addSublayer:rightShadow];

CAGradientLayer behind self.layer appearing above

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.