External Only Shadow on UIView
How to setup a CALayer shadow to show externally only with CALayer shadowPath and maskLayer
To add a shadow to a UIView
is very easy, all you have to do is drop down to the CALayer
underneath and setup it's shadow properties.
// Objective C
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0, 1);
view.layer.shadowRadius = 10;
view.layer.shadowOpacity = 1.0;
// Swift
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width:0, height:1);
view.layer.shadowRadius = 10;
view.layer.shadowOpacity = 1.0;
However, if your view has a partially, or completely, transparent background color this can look a bit weird.
Original:
// ObjC
view.backgroundColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:0.5];
// Swift
view.backgroundColor = UIColor(red:0, green:1, blue:0, alpha:0.5)
With shadow:
To fix this we can use use CALayer
's maskLayer
property to create an external only shadow.
First we need to create a mask layer the size and change of our original view.
// ObjC
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = view.bounds;
maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:view.layer.cornerRadius].CGPath;
// Swift
let maskLayer = CAShapeLayer()
maskLayer.frame = view.bounds
maskLayer.path = UIBezierPath(roundedRect: view.bounds, cornerRadius: view.layer.cornerRadius).cgPath
Then we need to move it back, up and make it bigger, so it won't clip the shadow itself.
// ObjC
CGFloat shadowBorder = (view.layer.shadowRadius * 2) + 5;
maskLayer.frame = CGRectInset( maskLayer.frame, -shadowBorder, -shadowBorder ); // Make bigger
maskLayer.frame = CGRectOffset( maskLayer.frame, shadowBorder/2.0, shadowBorder/2.0 ); // Move up and left
// Swift
// Make the mask area bigger than the view, so the shadow itself does not get clipped by the mask
let shadowBorder:CGFloat = (view.layer.shadowRadius * 2) + 5;
maskLayer.frame = maskLayer.frame.insetBy(dx: -shadowBorder, dy: -shadowBorder) // Make bigger
maskLayer.frame = maskLayer.frame.offsetBy(dx: shadowBorder/2, dy: shadowBorder/2) // Move up and left
And set the fill rule to allow cut outs inside the shape.
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
Lastly we create a mutable path and use it to rewrite the mask layer's path to one with a cut out a hole in the center where the original view was.
// ObjC
CGMutablePathRef pathMasking = CGPathCreateMutable();
// Add the outer view frame
CGPathAddPath(pathMasking, NULL, [UIBezierPath bezierPathWithRect:maskLayer.frame].CGPath);
// Translate into the shape back to the smaller original view's frame start point
CGAffineTransform catShiftBorder = CGAffineTransformMakeTranslation( shadowBorder/2.0, shadowBorder/2.0);
// Now add the original path for the cut out the shape of the original view
CGPathAddPath(pathMasking, NULL, CGPathCreateCopyByTransformingPath(maskLayer.path, &catShiftBorder ) );
// Set this big rect with a small cutout rect as the mask
maskLayer.path = pathMasking;
// Swift
let pathMasking = CGMutablePath()
// Add the outer view frame
pathMasking.addPath(UIBezierPath(rect: maskLayer.frame).cgPath)
// Translate into the shape back to the smaller original view's frame start point
var catShiftBorder = CGAffineTransform(translationX: shadowBorder/2, y: shadowBorder/2)
// Now add the original path for the cut out the shape of the original view
pathMasking.addPath(maskLayer.path!.copy(using: &catShiftBorder)!)
// Set this big rect with a small cutout rect as the mask
maskLayer.path = pathMasking;
Not forgetting to actually set the view's maskLayer
to our new layer.
view.layer.mask = maskLayer
This has given us our transparent center, but it will also crop out the background color and any child views placed inside.
As a result, this technique requires a full size 'shadow view' added as the bottom most child of the view you want to add a shadow to, rather than applying this directly to the view itself.
Example Xcode Projects
There are Swift and Objective C example Xcode projects showing this technique available on Github: