I have the following code:
- (void)setupLayer {
self.faucetShape = [CAShapeLayer layer];
self.faucetShape.strokeColor = [[UIColor blackColor] CGColor];
self.faucetShape.lineWidth = 2;
self.faucetShape.fillColor = nil;
self.faucetShape.path = [self faucetPath].CGPath; // faucetPath returns a 4 point diamond shaped UIBezierPath*
self.faucet = [CAGradientLayer layer];
self.faucet.mask = self.faucetShape;
self.faucet.colors = #[(id)[UIColor redColor].CGColor, (id)[UIColor redColor].CGColor];
[self.layer addSublayer: self.faucet];
}
But it shows nothing. Not sure what I'm missing (other than my graphic).
(faucet and faucetPath are both properties)
UPDATE
OK, I guess a CAGradientLayer has to have a meaningful frame. I assume a CAShapeLayer's frame isn't as meaningful, since the path can go outside the frame. Adding the following line made it show up:
self.faucet.frame = CGRectMake(0, 0, 64, 64);
So NOW my question is, is there a way to avoid having to do that? I'd just like the frame of the layer to implicitly be that of the containing View/Layer.
You need to set the frame of the layers. The following code will draw a red circle:
- (void)setupLayer
{
CAShapeLayer *faucetShape = [CAShapeLayer layer];
faucetShape.frame = CGRectMake(0, 0, 100, 100);
faucetShape.strokeColor = [[UIColor blackColor] CGColor];
faucetShape.lineWidth = 2;
faucetShape.fillColor = nil;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, CGRectMake(0, 0, 100, 100));
faucetShape.path = path;
CGPathRelease(path);
CAGradientLayer *faucet = [CAGradientLayer layer];
faucet.frame = CGRectMake(10, 10, 100, 100);
faucet.mask = faucetShape;
faucet.colors = #[(id)[UIColor redColor].CGColor, (id)[UIColor redColor].CGColor];
[self.view.layer addSublayer: faucet];
}
Related
I'm trying to get to a result like this:
And I can't seem to the get there, this is the code I have:
if (size.width > self.name.bounds.size.width) {
CAGradientLayer *l = [CAGradientLayer layer];
CGRect myFrame = self.name.bounds;
myFrame.size.width = 10.0f;
l.frame = myFrame;
l.colors = #[(id)[UIColor whiteColor].CGColor, (id)[UIColor clearColor].CGColor];
l.startPoint = CGPointMake(0.0f, 0.0f);
l.endPoint = CGPointMake(1.0f, 0.0f);
self.name.layer.mask = l;
}
What I'm currently getting is this:
Any help?
The issue with your code is that you are fixing the layer size here.
myFrame.size.width = 10.0f;
The above line fixes the frame size to 10 pixel. So that the layer size will be going to 10 pixel. And the layer can only render on this width.
Just comment or remove above line to get the desired result.
So that your code will be now like
CAGradientLayer *l = [CAGradientLayer layer];
CGRect myFrame = self.name.bounds;
// myFrame.size.width = 10.0f;
l.frame = myFrame;
l.colors = #[(id)[UIColor whiteColor].CGColor, (id)[UIColor clearColor].CGColor];
l.startPoint = CGPointMake(0.0f, 0.0f);
l.endPoint = CGPointMake(1.0f, 0.0f);
self.name.layer.mask = l;
For the bonus - You can make this effect more smooth by adding more colours into the array and by changing the startPoint and endPoint of the layer.
CAGradientLayer *l = [CAGradientLayer layer];
CGRect myFrame = self.name.bounds;
l.frame = myFrame;
l.colors = #[(id)[UIColor lightGrayColor].CGColor, (id)[UIColor whiteColor].CGColor, (id)[UIColor clearColor].CGColor];
l.startPoint = CGPointMake(0.0, 0.5);
l.endPoint = CGPointMake(1.0, 0.5);
self.name.layer.mask = l;
My question is about filling animation one CAShapeLayer with another CAShapeLayer. So, I wrote some code that almost achieves my goal.
I created a based layer and fill it with another. Code below:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_shapeLayer = [CAShapeLayer layer];
[self drawBackgroundLayer];
}
// my main/backgound layer
- (void)drawBackgroundLayer {
_shapeLayer.frame = CGRectMake(0, 0, 250, 250);
_shapeLayer.lineWidth = 3;
_shapeLayer.strokeColor = [UIColor blackColor].CGColor;
_shapeLayer.fillColor = UIColor.whiteColor.CGColor;
_shapeLayer.path = [UIBezierPath bezierPathWithRect:_shapeLayer.bounds].CGPath;
[self.view.layer addSublayer:_shapeLayer];
[self createCircleLayer];
}
Create a circled mask layer for filling:
- (void)createCircleLayer {
_shapeLayer.fillColor = UIColor.greenColor.CGColor;
CAShapeLayer *layer = [CAShapeLayer layer];
CGFloat radius = _shapeLayer.bounds.size.width*sqrt(2)/2;
layer.bounds = CGRectMake(0, 0, 2*radius, 2*radius);
layer.position = CGPointMake(_shapeLayer.bounds.size.width/2, _shapeLayer.bounds.size.height/2);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:radius];
layer.path = path.CGPath;
layer.transform = CATransform3DMakeScale(0.1, 0.1, 1);
_shapeLayer.mask = layer;
}
// our result is next]
So, let's animate it. I just transform the layer to fill the whole "_shapeLayer".
- (void)animateMaskLayer:(CAShapeLayer *)maskLayer {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1.0)];
animation.duration = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[newLayer addAnimation:animation forKey:#"transform"];
}
Now, I want to do the same with this green layer (fill it with red color).
But when I use the mask I have red circle and the white background what is obviously.
My goal here is to have red circle and green background. Like normal filling with another color.
By using inverse mask won't help neither.
So, I just have no ideas how to do it.
If you suggest using:
[_shapeLayer addSublayer:layer];
it won't work as there will be not only square but different paths and clipToBounds is not an option.
Thank you for any help!
So, the solution is simple. I just use two layers and one mask layer.
First layer: Red layer I want to fill with green layer;
- (void)drawBackgroundLayer {
_shapeLayer.frame = CGRectMake(20, 20, 250, 250);
_shapeLayer.lineWidth = 3;
_shapeLayer.strokeColor = [UIColor blackColor].CGColor;
_shapeLayer.lineWidth = 3;
_shapeLayer.fillColor = UIColor.redColor.CGColor;
_shapeLayer.path = [UIBezierPath bezierPathWithRect:_shapeLayer.bounds].CGPath;
[self.view.layer addSublayer:_shapeLayer];
[self drawUpperLayer];
}
Second Layer: Green Layer I use for filling.
- (void)drawUpperLayer {
_upperLayer = [CAShapeLayer layer];
_upperLayer.frame = _shapeLayer.frame;
_upperLayer.lineWidth = 3;
_upperLayer.strokeColor = [UIColor blackColor].CGColor;
_upperLayer.fillColor = UIColor.greenColor.CGColor;
_upperLayer.path = [UIBezierPath bezierPathWithRect:_shapeLayer.bounds].CGPath;
[_shapeLayer.superlayer addSublayer:_upperLayer];
}
Mask Layer:
- (void)createMaskLayer {
CAShapeLayer *layer = [CAShapeLayer layer];
CGFloat radius = _upperLayer.bounds.size.width*sqrt(2)/2;
layer.bounds = CGRectMake(0, 0, 2*radius, 2*radius);
layer.fillColor = UIColor.blackColor.CGColor;
layer.position = CGPointMake(_upperLayer.bounds.size.width/2, _upperLayer.bounds.size.height/2);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:radius];
layer.path = path.CGPath;
layer.transform = CATransform3DMakeScale(0.1, 0.1, 1);
_upperLayer.mask = layer;
}
Animate mask layer:
- (void)animateMaskLayer:(CAShapeLayer *)maskLayer {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1.0)];
animation.duration = 2;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[maskLayer addAnimation:animation forKey:#"transform"];
}
So, resulted animation:
I am trying to draw lines on the top and bottom edge of a UIView. But the line does not get drawn all the way to the right edge of the view controller.
Following is the code I am using to draw the lines:
- (void)addBorders
{
CALayer *upperBorder = [CALayer layer];
CALayer *bottomBorder = [CALayer layer];
upperBorder.backgroundColor = [[UIColor colorWithRed:225/255.0 green:220/255.0 blue:214/255.0 alpha:1.0f] CGColor];
upperBorder.frame = CGRectMake(0, 0, CGRectGetWidth(self.recentTuneinView.frame), 0.5f);
bottomBorder.backgroundColor = [[UIColor colorWithRed:154/255.0 green:154/255.0 blue:154/255.0 alpha:1.0f] CGColor];
bottomBorder.frame = CGRectMake(0, 58.0f, CGRectGetWidth(self.recentTuneinView.frame), 0.5f);
[self.recentTuneinView.layer addSublayer:upperBorder];
[self.recentTuneinView.layer addSublayer:bottomBorder];
}
Here is an image showing the problem:
What am I missing in the code?
Thanks.
Adding sub layers is not a scalable solution like it creates problems when rotating the device or view size change.
My advise is to create a custom view and implement drawRect: something like this:
- (void)drawRect:(CGRect)iRect {
CGContextRef aContext = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(aContext, 0.5);
// Set Top Line Color
CGContextSetStrokeColorWithColor(aContext, [[UIColor colorWithRed:225/255.0 green:220/255.0 blue:214/255.0 alpha:1.0f] CGColor]);
// Top Line
CGContextMoveToPoint(aContext, 0, 0);
CGContextAddLineToPoint(aContext, iRect.size.width, 0);
// Set Bottom Line Color
CGContextSetStrokeColorWithColor(aContext, [[UIColor colorWithRed:225/255.0 green:220/255.0 blue:214/255.0 alpha:1.0f] CGColor]);
// Bottom Line
CGContextMoveToPoint(aContext, 0, 58.0);
CGContextAddLineToPoint(aContext, iRect.size.width, 58.0);
}
Here is the solution. Above code updated.
- (void)addBorders
{
CALayer *upperBorder = [CALayer layer];
CALayer *bottomBorder = [CALayer layer];
upperBorder.backgroundColor = [[UIColor colorWithRed:225/255.0 green:220/255.0 blue:214/255.0 alpha:1.0f] CGColor];
upperBorder.frame = CGRectMake(0, 0, CGRectGetWidth(self.recentTuneinView.frame) + 60, 0.5f);
bottomBorder.backgroundColor = [[UIColor colorWithRed:154/255.0 green:154/255.0 blue:154/255.0 alpha:1.0f] CGColor];
bottomBorder.frame = CGRectMake(0, 58.0f, CGRectGetWidth(self.recentTuneinView.frame) + 60, 0.5f);
[self.recentTuneinView.layer addSublayer:upperBorder];
[self.recentTuneinView.layer addSublayer:bottomBorder];
}
Update your code.
CALayer *upperBorder = [CALayer layer];
CALayer *rightBorder = [CALayer layer];
CALayer *bottomBorder = [CALayer layer];
upperBorder.backgroundColor = [[UIColor colorWithRed:225/255.0 green:220/255.0 blue:214/255.0 alpha:1.0f] CGColor];
rightBorder.backgroundColor = upperBorder.backgroundColor;
upperBorder.frame = CGRectMake(0, 0, CGRectGetWidth(recentTuneinView.frame), 0.5f);
rightBorder.frame = CGRectMake(CGRectGetWidth(recentTuneinView.frame)-0.5, 0, 0.5f,CGRectGetHeight(recentTuneinView.frame));
bottomBorder.backgroundColor = [[UIColor colorWithRed:154/255.0 green:154/255.0 blue:154/255.0 alpha:1.0f] CGColor];
bottomBorder.frame = CGRectMake(0, 58.0f, CGRectGetWidth(recentTuneinView.frame), 0.5f);
[recentTuneinView.layer addSublayer:upperBorder];
[recentTuneinView.layer addSublayer:rightBorder];
[recentTuneinView.layer addSublayer:bottomBorder];
Note: You have to add right sublayer frame also.
It seems that if a CALayer, descendant of some UIViewController's rotatingHeaderView or rotatingFooterView has a mask, then the mask doesn't work during autorotation:
The code to create this shape:
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 30, 20)].CGPath;
CALayer *l = [CALayer layer];
l.backgroundColor = [UIColor redColor].CGColor;
l.mask = mask;
l.frame = CGRectMake(0, 0, 30, 20);
[self.layer addSublayer:l];
Is there some way to fix or get around this?
I am using MKNetwrkKit. I have an imageView, I have added it as a subView to a UIView. I download an image using "UIImageView+MKNetworkKitAdditions.h". I add a mask to the ImageView and remove it from superView, before the download is completed, and I get a crash!
- (void) testUIImageViewDownloadCrash {
UIImageView *imageView = [[UIImageView alloc] init];
[imageView mk_setImageAtURL:[NSURL URLWithString:#"http://www.gravatar.com/avatar/a717291747c76567bb0f086e15ae6e43?s=32&d=identicon&r=PG"] onCompletion:^(BOOL success, BOOL fromCache) {
//
}];
UIView *view = [[UIView alloc] init];
[view addSubview:imageView];
[self addMaskToImageView:imageView];
[imageView removeFromSuperview];
}
Now, the weird thing is, this crash occurs only when I add the mask... here is the function that adds the mask.
- (CAGradientLayer*) addMaskToImageView:(UIImageView *) inImageView
{
CALayer *imageLayer = inImageView.layer;
CGRect imageBounds = imageLayer.bounds;
CGRect ellipseRect = CGRectInset(imageBounds, 4, 4);
CGPathRef maskPath = CGPathCreateWithEllipseInRect(ellipseRect, NULL);
if (maskPath)
{
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.bounds = imageBounds;
maskLayer.position = (CGPoint){CGRectGetMidX(imageBounds), CGRectGetMidY(imageBounds)};
maskLayer.path = maskPath;
maskLayer.fillColor = [UIColor blackColor].CGColor;
imageLayer.mask = maskLayer;
CFRelease(maskPath);
}
CAGradientLayer *gradientRingLayer = [self createGradientRing:imageBounds];
CALayer *containerLayer = [CALayer layer];
[imageLayer.superlayer addSublayer:containerLayer];
// Move container layer to the image position
containerLayer.position = imageLayer.position;
// Image is now at 0,0 in the container
imageLayer.position = CGPointZero;
[containerLayer addSublayer:gradientRingLayer];
[containerLayer addSublayer:imageLayer];
layer.shouldRasterize = YES;
layer.rasterizationScale = [[UIScreen mainScreen] scale];
layer.contentsScale = [[UIScreen mainScreen] scale];
containerLayer.shadowColor = [UIColor blackColor].CGColor;
containerLayer.shadowOpacity = 1.0f;
containerLayer.shadowOffset = CGSizeMake(2.0f, 5.0f);
containerLayer.shadowRadius = 7.5f;
// Create shadow at the righ place around the image
CGPathRef containerShadowPath = CGPathCreateWithEllipseInRect(imageLayer.frame, NULL);
containerLayer.shadowPath = containerShadowPath;
CFRelease(containerShadowPath);
return gradientRingLayer;
}
- (CAGradientLayer*)createGradientRing:(CGRect)inBounds {
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.anchorPoint = CGPointMake(0.5f, 0.5f);
gradient.position = CGPointMake(0.0f, 0.0f);
// gradient.startPoint = CGPointMake(0.0,0.0);
// gradient.endPoint = CGPointMake(0.0,0.0);
gradient.bounds = inBounds;
gradient.cornerRadius = 0.0;
gradient.colors = [NSArray arrayWithObjects:
(id)[UIColor ringLight].CGColor,
(id)[UIColor ringLight].CGColor,
nil];
CAShapeLayer *frameLayer = [CAShapeLayer layer];
CGPathRef framePath = CGPathCreateWithEllipseInRect(CGRectInset(inBounds, 0, 0), NULL);
if (framePath)
{
frameLayer.bounds = inBounds;
frameLayer.position = (CGPoint){CGRectGetMidX(inBounds), CGRectGetMidY(inBounds)};
frameLayer.path = framePath;
frameLayer.fillColor = [UIColor blackColor].CGColor;
CFRelease(framePath);
}
gradient.mask = frameLayer;
return gradient;
}
If I comment out adding the mask, it won't crash. Another thing that I don't understand is, after adding the mask, if do this:
NSLog(#"subviews count : %i",[view.subviews count]);
then I get 0! Where did the imageView go?
I observed that the addMask function is making a superView of the imageView null. I found out that the line [containerLayer addSublayer:imageLayer]; is the culprit. This is making the superView of the imageView null. But why? And how can I fix it?
Sometimes I get this exception too, and I don't know why.
-[__NSCFString sublayers]: unrecognized selector sent to instance
I tried on iOS 5 and iOS 6.