Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Masking CALayer shadow to outside of rect only

Tags:

ios

calayer

How would I mask CALayer with shadow, so that the shadow is only outside the path? I don't want shadow behind a transparent view.

shadowLayer.shadowOffset = CGSizeMake(x, y);
shadowLayer.shadowRadius = radius;
shadowLayer.shadowOpacity = opacity;
shadowLayer.shadowColor = color.CGColor;
shadowLayer.shadowPath = [UIBezierPath bezierPathWithRect:view.bounds].CGPath;

Thank you.

like image 428
unexpectedvalue Avatar asked May 01 '14 15:05

unexpectedvalue


2 Answers

I will answer my own question. Adding a new shadow layer for view. This should work for any shadowPath, if set correctly.

float radius = 8;
float opacity = 0.5f;
float x = 4;
float y = 6;
UIColor *color = [UIColor blackColor];

// Shadow layer
CALayer *shadowLayer = [CALayer layer];
shadowLayer.shadowOffset = CGSizeMake(x, y);
shadowLayer.shadowRadius = radius;
shadowLayer.shadowOpacity = opacity;
shadowLayer.shadowColor = color.CGColor;
shadowLayer.shadowPath = [UIBezierPath bezierPathWithRect:view.frame].CGPath; // Or any other path

// Shadow mask frame
CGRect frame = CGRectInset(view.layer.frame, -2*radius, -2*radius);
frame = CGRectOffset(frame, x, y);

// Translate shadowLayer shadow path to mask layer's coordinate system
CGAffineTransform trans = CGAffineTransformMakeTranslation(-view.frame.origin.x-x+2*radius,
                                                           -view.frame.origin.y-y+2*radius);

// Mask path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, nil, (CGRect){.origin={0,0}, .size=frame.size});
CGPathAddPath(path, &trans, shadowLayer.shadowPath);
CGPathCloseSubpath(path);

// Mask layer
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = frame;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.path = path;

shadowLayer.mask = maskLayer;

[view.layer.superlayer insertSublayer:shadowLayer below:view.layer];
like image 191
unexpectedvalue Avatar answered Oct 15 '22 00:10

unexpectedvalue


ssteinberg's answer helped me in iOS 10.2 and Swift 3. I was able to use his answer with a UIVisualEffectView to create a beautiful blur but also with a shadow. Converted to Swift 3 here:

let shadowLayer = CALayer()

let mutablePath = CGMutablePath()
let maskLayer = CAShapeLayer()

let xOffset = CGFloat(4)
let yOffset = CGFloat(6)
let shadowOffset = CGSize(width: xOffset, height: yOffset)
let shadowOpacity = Float(0.5)
let shadowRadius = CGFloat(8)
let shadowPath = UIBezierPath(rect: frame).cgPath
let shadowColor = UIColor.black
let shadowFrame = frame.insetBy(dx: -2 * shadowRadius, dy: -2 * shadowRadius).offsetBy(dx: xOffset, dy: yOffset)
let shadowRect = CGRect(origin: .zero, size: shadowFrame.size)
let shadowTransform = CGAffineTransform(translationX: -frame.origin.x - xOffset + 2 * shadowRadius, y: -frame.origin.y - yOffset + 2 * shadowRadius)

shadowLayer.shadowOffset = shadowOffset
shadowLayer.shadowOpacity = shadowOpacity
shadowLayer.shadowRadius = shadowRadius
shadowLayer.shadowPath = shadowPath
shadowLayer.shadowColor = shadowColor.cgColor

mutablePath.addRect(shadowRect)
mutablePath.addPath(shadowLayer.shadowPath!, transform: shadowTransform)
mutablePath.closeSubpath()

maskLayer.frame = shadowFrame
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.path = mutablePath

shadowLayer.mask = maskLayer

layer.superlayer?.insertSublayer(shadowLayer, above: layer)
like image 38
Eric Armstrong Avatar answered Oct 14 '22 22:10

Eric Armstrong