Shadow not fully extending the width of a UIView - ios

I'm applying a background shadow on a UIView to give it the appearance of it being "on top of" the background (which is a MapView), however the shadow doesn't fully extend on the wider iPhone 6 and 6+ screens even though the UIView I'm applying it to does.
In viewDidLoad I am applying the shadow to the UIView using this code:
CALayer *layer = self.view_detailview.layer;
layer.shadowOffset = CGSizeMake(1, 1);
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowRadius = 4.0f;
layer.shadowOpacity = 0.80f;
layer.shadowPath = [[UIBezierPath bezierPathWithRect:layer.bounds] CGPath];
In my storyboard I have a constraint set to force the UIView width to match the superviews width which is working without any problems or warnings. But when I run my the app on a 6 or 6+ I see that the shadow doesn't fully extend like so:
I can verify that the UIView does use the full width by setting its background color to something noticeable and seeing it fill the screen.

The problem is that you are using the bounds value when the view hasn't been properly layout yet.
Set the shadow in viewDidLayoutSubviews (if using a view controller) or in layoutSubviews (if using a standalone view). In the second case, don't forget to call [super layoutSubviews].
Or don't change the shadowPath at all. Doesn't it work without that line?

Related

CALayer with different effects between iPhone / iPad

This has me a bit confused. I have a universal app which has two different Storyboards for iPhone and iPad. I'm using a small block of CALayer code to round the edges of a view and throw a shadow on it. Works perfectly on the iPhone.
[self configShadowLayer:_view
cornerRadius:_view.frame.size.width / 2.0
shadowOffsetX:0.0
shadowOffsetY:3.0
shadowRadius:2.0
opacity:0.2];
-(void)configShadowLayer:(UIView *)shadowView
cornerRadius:(float)cornerRadius
shadowOffsetX:(float)shadowOffsetX
shadowOffsetY:(float)shadowOffsetY
shadowRadius:(float)shadowRadius
opacity:(float)opacity {
CALayer *shadowLayer = shadowView.layer;
shadowLayer.masksToBounds = NO;
shadowLayer.cornerRadius = cornerRadius;
shadowLayer.shadowOffset = CGSizeMake(shadowOffsetX, shadowOffsetY);
shadowLayer.shadowRadius = shadowRadius;
shadowLayer.shadowOpacity = opacity;
}
On the iPad, it gives me a diamond shape. I have to change the corner radius from the half to a quarter and then it works. But then of course the iPhone view goes from a circle to a rounded rect.
What am I missing?
You should call configShadowLayer:cornerRadius:shadowOffsetX:shadowOffsetY:shadowRadius:opacity: after the layout of the view is done. When you call it only before the layout process the view's frame has probably the wrong sizes, and you will get a corner radius which is too large.
BTW: You should compute the radius depending to the bounds of the view, because the bounds represents the coordinate system inside of the view.

How can I add a drop shadow effect beneath image views on iOS?

I want to add a drop shadow effect underneath my UIImageView instances, like in the screenshot below:
How can I achieve this, either through storyboards or code (Objective-C)?
//Drop Shadow
[view.layer setShadowColor: [UIColor grayColor].CGColor];
[view.layer setShadowOpacity:0.8];
[view.layer setShadowRadius:3.0];
[view.layer setShadowOffset:CGSizeMake(2.0, 2.0)];
Please Try this once :-
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;
Explanation :-
As seen in the last step there are a few different properties needed when adding a shadow using Core Animation. If your are not familiar with Core Animation don’t worry, just try to focus on the shadow code and not the CALayer on the view.
shadowOffset is a CGSize representing how far to offset the shadow from the path.
shadowColor is the color of the shadow. Shadow colors should always be opaque, because the opacity will be set by the shadowOpacity property. The shadowColor property is a CGColor not a UIColor.
shadowRadius is the width of the shadow along the shadow path
shadowOpacity determines the opacity of the shadow.
shadowPath is probably the most important of the properties. While a shadow can be drawn without specifying a path, for performance reasons you should always specify one. This path tells Core Animation what the opaque regions of the view are, and without it, things slow down severely! It is a CGPath, which is most easily created using UIBezierPath (iOS only) as shown in step 2.
See more in http://nscookbook.com/2013/01/ios-programming-recipe-10-adding-a-shadow-to-uiview/
Those are not lights, but shadow. You can use the layer to draw shadow for UI elements.

CALayer in awakeFromNib vs drawRect

I'm very confused, why does the following code work if I add it to awakeFromNib or initWithFrame:, but doesn't work if I add it to drawRect: or call it directly?
self.layer.cornerRadius = CGRectGetWidth(self.bounds) / 2.0f;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowRadius = 3;
self.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
self.layer.shadowOpacity = 0.75f;
For programatically created buttons, where should I add this method to? The button might be created with just init and size changed later via constraints.
Specification: By working, I mean the button will be rounded (circle if aspect ratio is 1:1) with drop shadow. By not working, I mean it'll remain a square.
Check out the detailed description in the Apple Docs, but essentially it's because you're setting your layer configurations (cornerRadius, shadow, etc.) in the middle of the draw cycle when you should have completed this before the draw cycle began.
From the drawRect: documentation:
By the time this method is called, UIKit has configured the drawing environment appropriately for your view and you can simply call whatever drawing methods and functions you need to render your content.
Other functions, like awakeFromNib: or initWithFrame: occur before the draw cycle, meaning your configurations will be taken into account before they are rendered on screen. By comparison, drawRect: assumes these basic configurations are already set and just works on rendering what you've specified on screen.

iOS - how to create a "material design" compliant UIView with shadow?

I'm trying to see if there's a way to create a UIView with a shadow behavior compliant with material design. My understanding is that the shadow gets more intense as the object is further removed from the background surface.
I can manually add a shadow like this, however this shadow does not calculate how intense the shadow should be (based on Z order).
button.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:button.layer.bounds cornerRadius:11].CGPath;
button.layer.shadowOpacity = 1.0;
button.layer.shadowOffset = CGSizeMake(1,1);
button.layer.shadowColor = [UIColor blackColor].CGColor;
How do I create a UIView which will behave like Google's material design? I found this example, called Material Kit, however it simply hides shadow on touch
One way in which Google does this is by emphasizing the use of a
‘z-axis’ to create a sense of depth in which elements can occupy
different levels. In order to show that an element is occupying a
higher level on the z-axis, shadows are used around its border, being
cast on to the level below. What this creates is the illusion of an
interactive layer/element that exists above a different, lower
interactive layer/element.
You could subclass UIButton, and first set the shadow properties to your liking:
button.layer.shadowOffset = CGSizeMake(0, 1);
button.layer.shadowRadius = 5;
button.layer.shadowOpacity = 0;
Then change the shadowOpacity property when the highlighted state changes:
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
self.layer.shadowOpacity = (highlighted ? 0.85 : 0);
}
In order to apply both a shadow and a rounded corner, you need to use 2 nested views. This is because the two effects require competing masksToBounds properties.
Nest 2 UIViews. Apply the shadow to the outer view, setting masksToBounds = false. Then apply the rounded corner to the inner view, which requires masksToBounds = true.
Note that, according to Apple docs, masksToBounds is equivalent to clipsToBounds.

iOS drawing in CALayers drawInContext is either pixelated or blurry on retina

I have a custom UIView that draws something in an overwritten
- (void)drawRect:(CGRect)rect
this works fine and gives sharp results on retina screens.
However, now I would like to make the properties on which the drawing is based animatable. Creating animatable properties seems to be possible only on the CALayer, so instead of doing the drawing in UIView, I create a custom CALayer subclass and do the drawing inside
- (void) drawInContext:(CGContextRef)ctx
I use pretty much the exact same drawing code in this function as I used in the drawRect function of the custom UIView.
The result looks the same - however, it's not retina resolution but pixelated (large square pixels)
if I put
self.contentsScale = [UIScreen mainScreen].scale;
To the beginning of my drawInContext implementation, then instead of a pixelated result, I get a blurry result (as if the rendering is still performed in non-retina resolution and then upscaled to retina resolution).
what's the correct way to render sharp retina paths in CALayers drawInContext ?
here are some screenshots (the blue line is part of the custom drawing in question. the yellow part is just an image)
Drawn inside custom UIView's drawRect:
Drawn inside custom CALayer's drawInContext:
Drawin inside custom CALayer's drawInContext, with setting self.contentScale first:
For completeness, here's (a stripped down version of the) drawing code:
//if drawing inside custom UIView sublcass:
- (void)drawRect:(CGRect)rect
{
CGContextRef currenctContext = UIGraphicsGetCurrentContext();
[[UIColor blackColor] set];
CGContextSetLineWidth(currenctContext, _lineWidth);
CGContextSetLineJoin(currenctContext,kCGLineJoinRound);
CGContextMoveToPoint(currenctContext,x1, y1);
CGContextAddLineToPoint(currenctContext,x2, y2);
CGContextStrokePath(currenctContext);
}
//if drawing inside custom CALayer subclass:
- (void) drawInContext:(CGContextRef)ctx {
{
//self.contentsScale = [UIScreen mainScreen].scale;
CGContextRef currenctContext = ctx;
CGContextSetStrokeColorWithColor(currenctContext, [UIColor blackColor].CGColor);
CGContextSetLineWidth(currenctContext, _lineWidth);
CGContextSetLineJoin(currenctContext,kCGLineJoinRound);
CGContextMoveToPoint(currenctContext,x1, y1);
CGContextAddLineToPoint(currenctContext,x2, y2);
CGContextStrokePath(currenctContext);
}
To restate what I want to achieve: I want to achieve the same crisp retina rendering as in the UIView approach, but when rendering in CALayer
The issue is most likely to be contentScale here; be aware that if you're assigning this to a custom view by overriding its layerClass function, the layer's content scale may be reset. There may be some other instances in which this also happens. To be safe, set the content scale only after the layer has been added to a view.
Try assigning the main screen's scale to your custom layer during your custom view's init method. In Swift 3 that looks like this:
layer.contentsScale = UIScreen.mainScreen().scale
Or, in Swift 4:
layer.contentsScale = UIScreen.main.scale
Are you using shouldRasterize = YES in the layer? Try drawing in the CALayer subclass but set the rasterizationScale to the screen's scale.
After adding layer to its superlayer. set shouldRasterize to YES , set contentsScale and resterizatioinScale to screen scale:
[self.progressView.layer addSublayer:self.progressLayer];
self.progressLayer.shouldRasterize = YES;
self.progressLayer.contentsScale = kScreenScale;
self.progressLayer.rasterizationScale = kScreenScale;
CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:#"progress"];// progress is a customized property of progressLayer
animate.duration = 1.5;
animate.beginTime = 0;
animate.fromValue = #0;
animate.toValue = #1;
animate.fillMode = kCAFillModeForwards;
animate.removedOnCompletion = NO;
animate.repeatCount = HUGE_VALF;
[self.progressLayer addAnimation:animate forKey:#"progressAnimation"];

Resources