What's the best way to add a drop shadow to my UIView

Wez picture Wez · Mar 18, 2012 · Viewed 141k times · Source

I am trying to add a drop shadow to views that are layered on top of one another, the views collapse allowing content in other views to be seen, in this vein i want to keep view.clipsToBounds ON so that when the views collapse their content is clipped.

This seems to have made it difficult for me to add a drop shadow to the layers as when i turn clipsToBounds ON the shadows are clipped also.

I have been trying to manipulate view.frame and view.bounds in order to add a drop shadow to the frame but allow the bounds to be large enough to encompass it, however I have had no luck with this.

Here is the code I am using to add a Shadow (this only works with clipsToBounds OFF as shown)

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

Here is a screenshot of the shadow being applied to the top lightest grey layer. Hopefully this gives an idea of how my content will overlap if clipsToBounds is OFF.

Shadow Application.

How can I add a shadow to my UIView and keep my content clipped?

Edit: Just wanted to add that I have also played around with using background images with shadows on, which does work well, however I would still like to know the best coded solution for this.

Answer

pkluz picture pkluz · Mar 18, 2012

Try this:

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;

First of all: The UIBezierPath used as shadowPath is crucial. If you don't use it, you might not notice a difference at first, but the keen eye will observe a certain lag occurring during events like rotating the device and/or similar. It's an important performance tweak.

Regarding your issue specifically: The important line is view.layer.masksToBounds = NO. It disables the clipping of the view's layer's sublayers that extend further than the view's bounds.

For those wondering what the difference between masksToBounds (on the layer) and the view's own clipToBounds property is: There isn't really any. Toggling one will have an effect on the other. Just a different level of abstraction.


Swift 2.2:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath
}

Swift 3:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath
}