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

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.

Related

Shadow on UITableViewCell is giving bad performance

I'm creating table view with custom cells, which looks like news feed - on gray background there are rectangles with rounded corners and shadow.
While cellForRowAtIndexPath method is calling I'm setting shadow like this:
cell.postCard.layer.cornerRadius = 3
cell.postCard.layer.shadowColor = UIColor.darkGrayColor().CGColor
cell.postCard.layer.shadowOffset = CGSizeMake(0,1)
cell.postCard.layer.shadowRadius = 3
cell.postCard.layer.shadowOpacity = 0.5
cell.layer.shadowPath = UIBezierPath(rect: cell.postCard.layer.bounds).CGPath
Where postCard is UIView - it's container for all context of cell.
I've read that I need to add shadowPath to get good performance but I think it's not working because when I'm slowly moving table view is stuttering. Can be reason of using simulator than real device?
You are setting shadow path to cell's layer, while modifying shadow settings for cell.postCard
cell.postCard.layer.shadowOpacity = 0.5
cell.layer.shadowPath = UIBezierPath(rect: cell.postCard.layer.bounds).CGPath
Is this really what you need?
Also, you should set 'cell.clipsToBounds = false' for your cell.
Try to read about the should​Rasterize and how it is working with shadows and corner radius of the UI elements. You can boost performance by manipulation of the should​Rasterize value.

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.

Shadow not fully extending the width of a UIView

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?

UITableView shadow top?

I know there have been many questions on this before however none seem to work in my scenario. Pretty much I am trying to make the top maybe 10 points below the top of the frame of my tableview somewhat darker so have a nicer effect than just the cells scrolling off the frame.
Pretty much I need to accomplish an effect where the alpha starts at 0 and ends at 1 of a gray color which is 10 points high. This way there is some sort of subtle area before the top of the frame so that it doesn't look like the cells are just moving out of the frame.
Is this possible?
Thanks!
You can set a shadow to the navigationBar layer, assuming you are using one.
self.navigationController.navigationBar.layer.shadowColor = [[UIColor blackColor] CGColor];
self.navigationController.navigationBar.layer.shadowOffset = CGSizeMake(0.0f,0.0f);
self.navigationController.navigationBar.layer.shadowOpacity = 1.0f;
self.navigationController.navigationBar.layer.shadowRadius = 4.0f;
If you are not using a navigation controller, then you can apply this same type of shadow to a UIView's layer.

iOS: drop shadow with sharp edges

I'm trying to add some shadows to one of my views and what I would like to achieve is drawing shadows only on one side and let them have sharp edges. No I've tried quite a few methods without any luck (using the shadow related properties of the view's CALayer + UIBezierPaths). However, iOS is always rendering a shadow with soft edges like this:
But what I really want to acchieve is something like this (without round corners and sharp edges on the sides except one):
Is there any elegant way to do this or will I have to draw the shadow myself using CoreGraphics?
PS: I forgot to mention, my view should actually be a custom UIButton, so overriding drawRect: would be a pain here
I've experienced a mask removing the shadow from view...so maybe you can try that.
CALayer *mask = [[[CALayer alloc] init] autorelease];
mask.backgroundColor = [UIColor blackColor].CGColor;
mask.frame = CGRectMake(0.0, 0.0, yellowView.bounds.size.width + shadowWidth, yellowView.bounds.size.height);
yellowView.layer.mask = mask;
I think what you want to be changing is the shadowRadius value - set that to zero and you should get the sharp edge you're looking for:
myView.layer.shadowRadius = 0;

Resources