How to add a shadow to the outside of a CALayer object? - ios

I'm not sure if this is a duplicate question, but feel free to flag if it it is.
I'm trying to add a shadow to the outside of a CALayer. I currently have a UIImageView within a CALayer that I've made with rounder corners as shown:
CALayer * l = [ImageView layer];
[l setMasksToBounds:YES];
[l setCornerRadius:10.0];
I've tried to add the additional code:
[l setShadowOffset:CGSizeMake(0, 3)];
[l setShadowOpacity:0.4];
[l setShadowRadius:3.0f];
[l setShouldRasterize:YES];
The results are shown in RED where the shadow is applied within the CALayer instead of outside it, and that's what I'd like to achieve in green along the edges:
How can I achieve this while still maintain the rounded corners? Thanks

You can use UIBazierPath to achieve this:
UIBezierPath *shadow = [UIBezierPath bezierPathWithRect:imageview.bounds];
imageview.layer.masksToBounds = NO;
imageview.layer.shadowColor = [UIColor redColor].CGColor;
imageview.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
imageview.layer.shadowOpacity = 0.5f;
imageview.layer.shadowPath = shadowPath.CGPath;
Here masksToBounds = NO so sublayer of layer can extend boundry of view for shadow effect.

Related

Setting CALayer maskToBounds causing layer to disappear

I am trying to create a circle which is filled depending upon a certain percentage. Here is pic of the effect I am going for:
I have a UIView and to this I add a CAShapeLayer which draws the circle. I am then creating another UIShapeLayer as a square to match the UIView containing the circle. Then setting the height of this depending on the figure. So if the square is 100px high and the value is 10% then I set the square to 10px so that it fills 10% of circle.
The image below shows 50% of the circle being filled. As you can see it covers the UIView as well as the circle. However when I try to set the CAShapeLayer to mask to bounds ( circle.maskToBounds) the circle complete disappears along with the square that I am adding as it's subview.
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
[self drawCircleInView:view];
[self.view addSubview:view];
}
- (void)drawCircleInView:(UIView *)v
{
// Set up the shape of the circle
int radius = v.frame.size.height / 2;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2*radius, 2*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(v.bounds)-radius,
CGRectGetMidY(v.bounds)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor blueColor].CGColor;
circle.strokeColor = [UIColor clearColor].CGColor;
circle.lineWidth = 5;
circle.masksToBounds = YES; // HERE IS THE LINE OF CODE THAT MAKES THE CIRCLE DISAPPEAR
CAShapeLayer *sublayer = [CAShapeLayer layer];
sublayer.backgroundColor = [UIColor orangeColor].CGColor;
sublayer.opacity = .5f;
sublayer.frame = CGRectMake(0, 100, 200, 100);
[circle addSublayer:sublayer];
// Add to parent layer
[v.layer addSublayer:circle];
}
I am wondering why setting circle.masksToBounds = YES is making the circle and the sublayer disappear completely. My understanding is that by setting this it should only show the sublayer over the circle.
Many thanks in advance.
Try this:
- (void)drawCircleInView:(UIView *)v {
// Set up the shape of the circle
CGSize size = v.bounds.size;
CALayer *layer = [CALayer layer];
layer.frame = v.bounds;
layer.backgroundColor = [UIColor blueColor].CGColor;
CALayer *sublayer = [CALayer layer];
sublayer.frame = CGRectMake(0, size.height/2, size.width, size.height/2);
sublayer.backgroundColor = [UIColor orangeColor].CGColor;
sublayer.opacity = .5f;
CAShapeLayer *mask = [CAShapeLayer layer];
mask.frame = v.bounds;
mask.path = [UIBezierPath bezierPathWithOvalInRect:v.bounds].CGPath;
mask.fillColor = [UIColor blackColor].CGColor;
mask.strokeColor = [UIColor clearColor].CGColor;
[layer addSublayer:sublayer];
[layer setMask:mask];
// Add to parent layer
[v.layer addSublayer:layer];
}
maskToBounds is "to bounds" you are not setting bounds nor frame.
It's not necessary, but you'd better to set frame of CAShapeLayer, to prevent unnecessary confusion.
bounds is rectangle, not CAShapeLayer's shape. Use mask layer to mask by shape instead.
circle and sublayer is not need to be CAShapeLayer because it will be masked by shaped mask layer.
To achieve this use a square image with its center circle as transparent. like this image
How to use this image for this effect -
Add a square layer (with lighter color/ or non fill color) first. above that add a layer with the fill color with sane rect. and above all place this image. and change the frame of middle layer that has the fill color to achieve desired effect

Glossy Label Look in iOS

I am looking at how to make a label appear to have a rounded glossy look, similar to this:
I know I can use the CALayer in the app for different things like borders, and rounded corners, but I'm unsure about a glossy look. Right now I am using:
CALayer* l = [myCounterLabel layer];
CALayer* m = [myhiddenlabel layer];
[l setMasksToBounds:YES];
[l setCornerRadius:11];
[l setBorderWidth:2.0];
[l setBorderColor:[[UIColor blackColor] CGColor]];
This gives me a label that looks like this (I know font and everything else doesn't match...right now just looking for a glossy look:
There's a few ways you could do this but a CAGradientLayer is likely your best bet. Assume you have CAGradientLayer* property:
self.gradient = [CAGradientLayer layer];
self.gradient.frame = CGRectMake(100, 100, 100, 100);
self.gradient.cornerRadius = 11;
self.gradient.borderWidth = 2.0;
// Set the colors you want in the gradient
self.gradient.colors = #[(id)[UIColor grayColor].CGColor, (id)[UIColor grayColor].CGColor, (id)[UIColor blackColor].CGColor, (id)[UIColor blackColor].CGColor];
// if you want to control as to the distribution of the gradient you can set the control points for the gradient here (make sure you have as many as you have colors)
self.gradient.locations = #[#0.0f, #0.5f, #0.5f, #1.0f];
You could try to add another sub-layer to your original layer with background color to fill the second half part:
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor; //your color
sublayer.frame = CGRectMake(0, 50, 50, 50); //parent layer (height 100, width 50)
[yourLayer addSublayer:sublayer];

How to create rounded UITextField with inner shadow

I'm customizing a UITextfield to look like a UISearchbar.
I do something like
self.back_textfield = [[UITextField alloc]initWithFrame:CGRectMake(5, 7, 310, 30)];
[self.back_textfield setBorderStyle:UITextBorderStyleRoundedRect];
self.back_textfield.layer.cornerRadius = 15.0;
But I see this:
As you can see the inner shadow doesn't follow the border.
I guess background on a UITextField is an Image, so it no follow to your corner radius.
Creating inner shadow is tricky in iOS. You have 2 options.
1) Use image as background for UITextField
2) Set the shadow programmatically (but it look less attractive than option 1).
Here is the code for setting rounded inner shadow for textfield with solution from #Matt Wilding
_textField.layer.cornerRadius = 10.0f;
CAShapeLayer* shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:_textField.bounds];
// Standard shadow stuff
[shadowLayer setShadowColor:[[UIColor colorWithWhite:0 alpha:1] CGColor]];
[shadowLayer setShadowOffset:CGSizeMake(0.0f, 0.0f)];
[shadowLayer setShadowOpacity:1.0f];
[shadowLayer setShadowRadius:4];
// Causes the inner region in this example to NOT be filled.
[shadowLayer setFillRule:kCAFillRuleEvenOdd];
// Create the larger rectangle path.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectInset(_textField.bounds, -42, -42));
// Add the inner path so it's subtracted from the outer path.
// someInnerPath could be a simple bounds rect, or maybe
// a rounded one for some extra fanciness.
CGPathRef someInnerPath = [UIBezierPath bezierPathWithRoundedRect:_textField.bounds cornerRadius:10.0f].CGPath;
CGPathAddPath(path, NULL, someInnerPath);
CGPathCloseSubpath(path);
[shadowLayer setPath:path];
CGPathRelease(path);
[[_textField layer] addSublayer:shadowLayer];
CAShapeLayer* maskLayer = [CAShapeLayer layer];
[maskLayer setPath:someInnerPath];
[shadowLayer setMask:maskLayer];
Don't forget to import
#import <QuartzCore/QuartzCore.h>
add QuartzCore FrameWork to your project
And add it in your .m file
#import <QuartzCore/QuartzCore.h>
and use
self.back_textfield.layer.borderWidth = 1.0f; // set as you per your requirement
self.back_textfield.layer.cornerRadius = 5.2f; // set as you per your requirement

Why don't added sublayers show up in screenshot?

I'm trying to track down a bug in some iOS code for an iPad app. In one of our views, we've added sublayers to have a shadow and make sure the bottom of the view has rounded edges. Here's the code where we add the sublayers:
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds
byRoundingCorners:(UIRectCornerBottomLeft|UIRectCornerBottomRight)
cornerRadii:CGSizeMake(12.0f, 12.0f)];
// Create the shadow layer
shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:self.bounds];
[shadowLayer setMasksToBounds:NO];
[shadowLayer setShadowPath:maskPath.CGPath];
shadowLayer.shadowColor = [UIColor blackColor].CGColor;
shadowLayer.shadowOffset = CGSizeMake(0.0f, 0.0f);
shadowLayer.shadowOpacity = 0.5f;
shadowLayer.shadowRadius = 6.0f;
roundedLayer = [CALayer layer];
[roundedLayer setFrame:self.bounds];
[roundedLayer setBackgroundColor:[UIColor colorFromHex:#"#e4ecef"].CGColor];
[self.layer insertSublayer:shadowLayer atIndex:0];
// Add inner view (since we're rounding corners, parent view can't mask to bounds b/c of shadow - need extra view)
maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
innerView = [[UIView alloc] initWithFrame:self.bounds];
innerView.backgroundColor = [UIColor whiteColor];
innerView.layer.mask = maskLayer;
[self addSubview:innerView];
It shows up fine on the screen of the iPad, but I want to take a screenshot programmatically. I've added a category to UIView with this method:
- (UIImage*)screenshot {
UIGraphicsBeginImageContext(self.frame.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(viewImage, nil, nil, nil);
return viewImage;
}
When I look at the screenshot that is taken, it no longer has the rounded corners or the shadow behind my view. Why aren't they showing up?
Found an explanation here: CALayer renderInContext
Additionally, layers that use 3D transforms are not rendered, nor are
layers that specify backgroundFilters, filters, compositingFilter, or
a mask values.
It looks like some sublayers can't be handled by renderInContext, which is why they aren't showing up in my screenshots.
Gradient sublayers
The snapshot is not taking any gradient layers with alpha channel in it.
Remove the alpha from colors, works for me

How to remove sharp rectangle edges from UIButton control?

Take a look at the following image:
As you can see the edges are sharp rectangle and they are coming out of the rounded corner button. How can I not show the edges and only show the round button?
UPDATE (Solution):
I used the following code to achieve the round corners:
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.stopButton.bounds
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(12.0, 12.0)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.stopButton.bounds;
maskLayer.path = maskPath.CGPath;
// Set the newly created shape layer as the mask for the image view's layer
self.stopButton.layer.mask = maskLayer;
NEW PROBLEM:
This code gives me the error on the startButtonRound.layer.cornerRadius line.
UIButton *roundStartButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
roundStartButton.frame = CGRectMake(10, 10, 100, 20);
[roundStartButton setBackgroundImage:[UIImage imageNamed:#"green_gradient.jpg"] forState:UIControlStateNormal];
roundStartButton.layer.cornerRadius = 12.0;
UIBarButtonItem *startButton = [[UIBarButtonItem alloc] initWithCustomView:roundStartButton];
You could do this with CALayer alone:
CALayer *myLayer = [CALayer layer];
[myLayer setMasksToBounds:YES];
[myLayer setCornerRadius:5.0f];
[myLayer setBorderWidth:1.0f];
[myLayer setBorderColor: [[UIColor blackColor] CGColor]];
[myLayer setBackgroundColor: [[UIColor whiteColor] CGColor]];
You'll also want to include the QuartzCore framework and include the frameworks header in your own.
#include <QuartzCore/QuartzCore.h>
Hope this helps
Tim
I think all that can be accomplished with:
self.stopButton.layer.cornerRadius = 12.0;
But did you try
self.stopButton.opaque = NO;
self.stopButton.backgroundColor = [UIColor clearColor];
?
form the look of the image you have set your UIButton bckground color to 'White'. Make it 'Clear Color'.

Resources