UIView 图层的内阴影效果? ?

我有以下的煤层气:

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(8, 57, 296, 30);
gradient.cornerRadius = 3.0f;
gradient.colors = [NSArray arrayWithObjects:(id)[RGB(130, 0, 140) CGColor], (id)[RGB(108, 0, 120) CGColor], nil];
[self.layer insertSublayer:gradient atIndex:0];

我想添加一个 内心的阴影的效果,但我不太确定如何做到这一点。我想我会被要求绘制 DrawRect,但是这将添加图层上的其他 UIView 对象,因为它应该是一个酒吧后面的一些按钮,所以我不知道该怎么办?

我可以添加另一个图层,但同样,不知道如何实现内阴影效果(像这样:

enter image description here

感谢你的帮助。

84176 次浏览

It is possible to draw an inner shadow with Core Graphics by making a large rectangle path outside the bounds, subtracting a bounds-sized rectangle path and filling the resulting path with a "normal" shadow on.

However, since you need to combine it with a gradient layer, I think an easier solution is to create a 9-part transparent PNG image of the inner shadow and stretch it to the right size. The 9-part shadow image would look like this (its size is 21x21 pixels):

alt text

CALayer *innerShadowLayer = [CALayer layer];
innerShadowLayer.contents = (id)[UIImage imageNamed: @"innershadow.png"].CGImage;
innerShadowLayer.contentsCenter = CGRectMake(10.0f/21.0f, 10.0f/21.0f, 1.0f/21.0f, 1.0f/21.0f);

Then set innerShadowLayer's frame and it should stretch the shadow properly.

For anyone else wondering how to draw an inner shadow using Core Graphics as per Costique's suggestion, then this is how: (on iOS adjust as needed)

In your drawRect: method...

CGRect bounds = [self bounds];
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radius = 0.5f * CGRectGetHeight(bounds);




// Create the "visible" path, which will be the shape that gets the inner shadow
// In this case it's just a rounded rect, but could be as complex as your want
CGMutablePathRef visiblePath = CGPathCreateMutable();
CGRect innerRect = CGRectInset(bounds, radius, radius);
CGPathMoveToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x + innerRect.size.width, bounds.origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, bounds.origin.y, bounds.origin.x + bounds.size.width, innerRect.origin.y, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, innerRect.origin.y + innerRect.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height, innerRect.origin.x + innerRect.size.width, bounds.origin.y + bounds.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y + bounds.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y + bounds.size.height, bounds.origin.x, innerRect.origin.y + innerRect.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x, innerRect.origin.y);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y, innerRect.origin.x, bounds.origin.y, radius);
CGPathCloseSubpath(visiblePath);


// Fill this path
UIColor *aColor = [UIColor redColor];
[aColor setFill];
CGContextAddPath(context, visiblePath);
CGContextFillPath(context);




// Now create a larger rectangle, which we're going to subtract the visible path from
// and apply a shadow
CGMutablePathRef path = CGPathCreateMutable();
//(when drawing the shadow for a path whichs bounding box is not known pass "CGPathGetPathBoundingBox(visiblePath)" instead of "bounds" in the following line:)
//-42 cuould just be any offset > 0
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));
    

// Add the visible path (so that it gets subtracted for the shadow)
CGPathAddPath(path, NULL, visiblePath);
CGPathCloseSubpath(path);


// Add the visible paths as the clipping path to the context
CGContextAddPath(context, visiblePath);
CGContextClip(context);




// Now setup the shadow properties on the context
aColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 3.0f, [aColor CGColor]);


// Now fill the rectangle, so the shadow gets drawn
[aColor setFill];
CGContextSaveGState(context);
CGContextAddPath(context, path);
CGContextEOFillPath(context);


// Release the paths
CGPathRelease(path);
CGPathRelease(visiblePath);

So, essentially there are the following steps:

  1. Create your path
  2. Set the fill color you want, add this path to the context, and fill the context
  3. Now create a larger rectangle that can bound the visible path. Before closing this path, add the visible path. Then close the path, so that you create a shape with the visible path subtracted from it. You might want to investigate the fill methods (non-zero winding of even/odd) depending on how you created these paths. In essence, to get the subpaths to "subtract" when you add them together, you need to draw them (or rather construct them) in opposite directions, one clockwise and the other anti-clockwise.
  4. Then you need to set your visible path as the clipping path on the context, so that you don't draw anything outside it to the screen.
  5. Then setup up the shadow on the context, which includes the offset, blur and color.
  6. Then fill the big shape with the hole in it. The color doesn't matter, because if you've done everything right, you won't see this color, just the shadow.

Bit of a round-about way, but it avoids having to use images (read: easy to change colors, shadow radius, etc.) and it's only a few lines of code.

  1. Add a UIImageView as the first subview of the UIView you want the dropshadow on. I use IB, but you can do the same programmatically.

  2. Assuming the reference to the UIImageView is 'innerShadow'

`

[[innerShadow layer] setMasksToBounds:YES];
[[innerShadow layer] setCornerRadius:12.0f];
[[innerShadow layer] setBorderColor:[UIColorFromRGB(180, 180, 180) CGColor]];
[[innerShadow layer] setBorderWidth:1.0f];
[[innerShadow layer] setShadowColor:[UIColorFromRGB(0, 0, 0) CGColor]];
[[innerShadow layer] setShadowOffset:CGSizeMake(0, 0)];
[[innerShadow layer] setShadowOpacity:1];
[[innerShadow layer] setShadowRadius:2.0];

Caveat: You have to have a border, or else the shadow doesn't show up. [UIColor clearColor] doesn't work. In the example I use a different color, but you can mess with it to get it to have the same color as the beginning of the shadow. :)

See bbrame's comment below about the UIColorFromRGB macro.

I know I'm late to this party, but this would have helped me to find early in my travels...

To give credit where credit's due, this is essentially a modification of Daniel Thorpe's elaboration on Costique's solution of subtracting a smaller region from a larger region. This version is for those using layer composition instead of overriding -drawRect:

The CAShapeLayer class can be used to achieve the same effect:

CAShapeLayer *shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:[self bounds]];


// Standard shadow stuff
[shadowLayer setShadowColor:[[UIColor colorWithWhite:0 alpha:1] CGColor]];
[shadowLayer setShadowOffset:CGSizeMake(0.0f, 0.0f)];
[shadowLayer setShadowOpacity:1.0f];
[shadowLayer setShadowRadius:5];


// Causes the inner region in this example to NOT be filled.
[shadowLayer setFillRule:kCAFillRuleEvenOdd];


// Create the larger rectangle path.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));


// Add the inner path so it's subtracted from the outer path.
// someInnerPath could be a simple bounds rect, or maybe
// a rounded one for some extra fanciness.
CGPathAddPath(path, NULL, someInnerPath);
CGPathCloseSubpath(path);


[shadowLayer setPath:path];
CGPathRelease(path);


[[self layer] addSublayer:shadowLayer];

At this point, if your parent layer isn't masking to its bounds, you'll see the extra area of the mask layer around the edges of the layer. This will be 42 pixels of black if you just copied the example directly. To get rid of it, you can simply use another CAShapeLayer with the same path and set it as the mask of the shadow layer:

CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setPath:someInnerPath];
[shadowLayer setMask:maskLayer];

I haven't benchmarked this myself, but I suspect that using this approach in conjunction with rasterization is more performant than overriding -drawRect:.

I'm very late to the the party but I'd like to give back to the community.. This is a method I wrote to remove UITextField background Image as I was supplying a Static Library and NO Resources... I used this for a PIN Entry screen of four UITextField instances that could display One character raw or (BOOL)[self isUsingBullets] or (BOOL)[self usingAsterisks] in ViewController. App is for iPhone/iPhone retina/iPad/iPad Retina so I do not have to supply four images...

#import <QuartzCore/QuartzCore.h>


- (void)setTextFieldInnerGradient:(UITextField *)textField
{


[textField setSecureTextEntry:self.isUsingBullets];
[textField setBackgroundColor:[UIColor blackColor]];
[textField setTextColor:[UIColor blackColor]];
[textField setBorderStyle:UITextBorderStyleNone];
[textField setClipsToBounds:YES];


[textField.layer setBorderColor:[[UIColor blackColor] CGColor]];
[textField.layer setBorderWidth:1.0f];


// make a gradient off-white background
CAGradientLayer *gradient = [CAGradientLayer layer];
CGRect gradRect = CGRectInset([textField bounds], 3, 3);    // Reduce Width and Height and center layer
gradRect.size.height += 2;  // minimise Bottom shadow, rely on clipping to remove these 2 pts.


gradient.frame = gradRect;
struct CGColor *topColor = [UIColor colorWithWhite:0.6f alpha:1.0f].CGColor;
struct CGColor *bottomColor = [UIColor colorWithWhite:0.9f alpha:1.0f].CGColor;
// We need to use this fancy __bridge object in order to get the array we want.
gradient.colors = [NSArray arrayWithObjects:(__bridge id)topColor, (__bridge id)bottomColor, nil];
[gradient setCornerRadius:4.0f];
[gradient setShadowOffset:CGSizeMake(0, 0)];
[gradient setShadowColor:[[UIColor whiteColor] CGColor]];
[gradient setShadowOpacity:1.0f];
[gradient setShadowRadius:3.0f];


// Now we need to Blur the edges of this layer "so it blends"
// This rasterizes the view down to 4x4 pixel chunks then scales it back up using bilinear filtering...
// it's EXTREMELY fast and looks ok if you are just wanting to blur a background view under a modal view.
// To undo it, just set the rasterization scale back to 1.0 or turn off rasterization.
[gradient setRasterizationScale:0.25];
[gradient setShouldRasterize:YES];


[textField.layer insertSublayer:gradient atIndex:0];


if (self.usingAsterisks) {
[textField setFont:[UIFont systemFontOfSize:80.0]];
} else {
[textField setFont:[UIFont systemFontOfSize:40.0]];
}
[textField setTextAlignment:UITextAlignmentCenter];
[textField setEnabled:NO];
}

I hope this helps someone as this forum has helped me.

There is some code here which can do this for you. If you change the layer in your view (by overriding + (Class)layerClass), to JTAInnerShadowLayer then you can set the inner shadow on the indent layer up in your init method and it will do the work for you. If you also want to draw the original content make sure you call setDrawOriginalImage:yes on the indent layer. There's a blog post about how this works here.

Instead of drawing inner shadow by drawRect or add UIView to the View. You may directly Add CALayer to the border, for example: if I want inner shadow effect on UIView V's bottom.

innerShadowOwnerLayer = [[CALayer alloc]init];
innerShadowOwnerLayer.frame = CGRectMake(0, V.frame.size.height+2, V.frame.size.width, 2);
innerShadowOwnerLayer.backgroundColor = [UIColor whiteColor].CGColor;


innerShadowOwnerLayer.shadowColor = [UIColor blackColor].CGColor;
innerShadowOwnerLayer.shadowOffset = CGSizeMake(0, 0);
innerShadowOwnerLayer.shadowRadius = 10.0;
innerShadowOwnerLayer.shadowOpacity = 0.7;


[V.layer addSubLayer:innerShadowOwnerLayer];

This add a bottom inner shadow for target UIView

Better late than never...

Here's another approach, probably not any better than those already posted, but it's nice & simple -

-(void)drawInnerShadowOnView:(UIView *)view
{
UIImageView *innerShadowView = [[UIImageView alloc] initWithFrame:view.bounds];


innerShadowView.contentMode = UIViewContentModeScaleToFill;
innerShadowView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;


[view addSubview:innerShadowView];


[innerShadowView.layer setMasksToBounds:YES];


[innerShadowView.layer setBorderColor:[UIColor lightGrayColor].CGColor];
[innerShadowView.layer setShadowColor:[UIColor blackColor].CGColor];
[innerShadowView.layer setBorderWidth:1.0f];


[innerShadowView.layer setShadowOffset:CGSizeMake(0, 0)];
[innerShadowView.layer setShadowOpacity:1.0];


// this is the inner shadow thickness
[innerShadowView.layer setShadowRadius:1.5];
}

This is your solution, which I've exported from PaintCode :

-(void) drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();


//// Shadow Declarations
UIColor* shadow = UIColor.whiteColor;
CGSize shadowOffset = CGSizeMake(0, 0);
CGFloat shadowBlurRadius = 10;


//// Rectangle Drawing
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.bounds];
[[UIColor blackColor] setFill];
[rectanglePath fill];


////// Rectangle Inner Shadow
CGContextSaveGState(context);
UIRectClip(rectanglePath.bounds);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);


CGContextSetAlpha(context, CGColorGetAlpha([shadow CGColor]));
CGContextBeginTransparencyLayer(context, NULL);
{
UIColor* opaqueShadow = [shadow colorWithAlphaComponent: 1];
CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [opaqueShadow CGColor]);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextBeginTransparencyLayer(context, NULL);


[opaqueShadow setFill];
[rectanglePath fill];


CGContextEndTransparencyLayer(context);
}
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
}

A simplified version using just a CALayer, in Swift:

import UIKit


final class FrameView : UIView {
init() {
super.init(frame: CGRect.zero)
backgroundColor = UIColor.white
}


@available(*, unavailable)
required init?(coder decoder: NSCoder) { fatalError("unavailable") }


override func layoutSubviews() {
super.layoutSubviews()
addInnerShadow()
}


private func addInnerShadow() {
let innerShadow = CALayer()
innerShadow.frame = bounds
// Shadow path (1pt ring around bounds)
let path = UIBezierPath(rect: innerShadow.bounds.insetBy(dx: -1, dy: -1))
let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
path.append(cutout)
innerShadow.shadowPath = path.cgPath
innerShadow.masksToBounds = true
// Shadow properties
innerShadow.shadowColor = UIColor(white: 0, alpha: 1).cgColor // UIColor(red: 0.71, green: 0.77, blue: 0.81, alpha: 1.0).cgColor
innerShadow.shadowOffset = CGSize.zero
innerShadow.shadowOpacity = 1
innerShadow.shadowRadius = 3
// Add
layer.addSublayer(innerShadow)
}
}

Note that the innerShadow layer should not have an opaque background color as this will be rendered in front of the shadow.

Here is a version of swift, change startPoint and endPoint to make it on each side.

        let layer = CAGradientLayer()
layer.startPoint    = CGPointMake(0.5, 0.0);
layer.endPoint      = CGPointMake(0.5, 1.0);
layer.colors        = [UIColor(white: 0.1, alpha: 1.0).CGColor, UIColor(white: 0.1, alpha: 0.5).CGColor, UIColor.clearColor().CGColor]
layer.locations     = [0.05, 0.2, 1.0 ]
layer.frame         = CGRectMake(0, 0, self.view.frame.width, 60)
self.view.layer.insertSublayer(layer, atIndex: 0)

Check the great article of Inner Shadows in Quartz by Chris Emery wich explains how the inner shadows are drawn by PaintCode and gives a clean and neat code snippet:

- (void)drawInnerShadowInContext:(CGContextRef)context
withPath:(CGPathRef)path
shadowColor:(CGColorRef)shadowColor
offset:(CGSize)offset
blurRadius:(CGFloat)blurRadius
{
CGContextSaveGState(context);


CGContextAddPath(context, path);
CGContextClip(context);


CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);


CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
CGContextBeginTransparencyLayer(context, NULL);
CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextSetFillColorWithColor(context, opaqueShadowColor);
CGContextAddPath(context, path);
CGContextFillPath(context);
CGContextEndTransparencyLayer(context);


CGContextRestoreGState(context);


CGColorRelease(opaqueShadowColor);
}

Using Gradient Layer:

UIView * mapCover = [UIView new];
mapCover.frame = map.frame;
[view addSubview:mapCover];


CAGradientLayer * vertical = [CAGradientLayer layer];
vertical.frame = mapCover.bounds;
vertical.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
(id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
(id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
(id)[UIColor whiteColor].CGColor, nil];
vertical.locations = @[@0.01,@0.1,@0.9,@0.99];
[mapCover.layer insertSublayer:vertical atIndex:0];


CAGradientLayer * horizontal = [CAGradientLayer layer];
horizontal.frame = mapCover.bounds;
horizontal.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
(id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
(id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
(id)[UIColor whiteColor].CGColor, nil];
horizontal.locations = @[@0.01,@0.1,@0.9,@0.99];
horizontal.startPoint = CGPointMake(0.0, 0.5);
horizontal.endPoint = CGPointMake(1.0, 0.5);
[mapCover.layer insertSublayer:horizontal atIndex:0];

this code worked for me

class InnerDropShadowView: UIView {
override func draw(_ rect: CGRect) {
//Drawing code
let context = UIGraphicsGetCurrentContext()
//// Shadow Declarations
let shadow: UIColor? = UIColor.init(hexString: "a3a3a3", alpha: 1.0) //UIColor.black.withAlphaComponent(0.6) //UIColor.init(hexString: "d7d7da", alpha: 1.0)
let shadowOffset = CGSize(width: 0, height: 0)
let shadowBlurRadius: CGFloat = 7.5
//// Rectangle Drawing
let rectanglePath = UIBezierPath(rect: bounds)
UIColor.groupTableViewBackground.setFill()
rectanglePath.fill()
////// Rectangle Inner Shadow
context?.saveGState()
UIRectClip(rectanglePath.bounds)
context?.setShadow(offset: CGSize.zero, blur: 0, color: nil)
context?.setAlpha((shadow?.cgColor.alpha)!)
context?.beginTransparencyLayer(auxiliaryInfo: nil)
do {
let opaqueShadow: UIColor? = shadow?.withAlphaComponent(1)
context?.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: opaqueShadow?.cgColor)
context!.setBlendMode(.sourceOut)
context?.beginTransparencyLayer(auxiliaryInfo: nil)
opaqueShadow?.setFill()
rectanglePath.fill()
context!.endTransparencyLayer()
}
context!.endTransparencyLayer()
context?.restoreGState()
}
}

Here is my solution in Swift 4.2. Would you want to try?

final class ACInnerShadowLayer : CAShapeLayer {


var innerShadowColor: CGColor? = UIColor.black.cgColor {
didSet { setNeedsDisplay() }
}


var innerShadowOffset: CGSize = .zero {
didSet { setNeedsDisplay() }
}


var innerShadowRadius: CGFloat = 8 {
didSet { setNeedsDisplay() }
}


var innerShadowOpacity: Float = 1 {
didSet { setNeedsDisplay() }
}


override init() {
super.init()


masksToBounds = true
contentsScale = UIScreen.main.scale


setNeedsDisplay()
}


override init(layer: Any) {
if let layer = layer as? InnerShadowLayer {
innerShadowColor = layer.innerShadowColor
innerShadowOffset = layer.innerShadowOffset
innerShadowRadius = layer.innerShadowRadius
innerShadowOpacity = layer.innerShadowOpacity
}
super.init(layer: layer)
}


required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}


override func draw(in ctx: CGContext) {
ctx.setAllowsAntialiasing(true)
ctx.setShouldAntialias(true)
ctx.interpolationQuality = .high


let colorspace = CGColorSpaceCreateDeviceRGB()


var rect = bounds
var radius = cornerRadius


if borderWidth != 0 {
rect = rect.insetBy(dx: borderWidth, dy: borderWidth)
radius -= borderWidth
radius = max(radius, 0)
}


let innerShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
ctx.addPath(innerShadowPath)
ctx.clip()


let shadowPath = CGMutablePath()
let shadowRect = rect.insetBy(dx: -rect.size.width, dy: -rect.size.width)
shadowPath.addRect(shadowRect)
shadowPath.addPath(innerShadowPath)
shadowPath.closeSubpath()


if let innerShadowColor = innerShadowColor, let oldComponents = innerShadowColor.components {
var newComponets = Array<CGFloat>(repeating: 0, count: 4) // [0, 0, 0, 0] as [CGFloat]
let numberOfComponents = innerShadowColor.numberOfComponents


switch numberOfComponents {
case 2:
newComponets[0] = oldComponents[0]
newComponets[1] = oldComponents[0]
newComponets[2] = oldComponents[0]
newComponets[3] = oldComponents[1] * CGFloat(innerShadowOpacity)
case 4:
newComponets[0] = oldComponents[0]
newComponets[1] = oldComponents[1]
newComponets[2] = oldComponents[2]
newComponets[3] = oldComponents[3] * CGFloat(innerShadowOpacity)
default:
break
}


if let innerShadowColorWithMultipliedAlpha = CGColor(colorSpace: colorspace, components: newComponets) {
ctx.setFillColor(innerShadowColorWithMultipliedAlpha)
ctx.setShadow(offset: innerShadowOffset, blur: innerShadowRadius, color: innerShadowColorWithMultipliedAlpha)
ctx.addPath(shadowPath)
ctx.fillPath(using: .evenOdd)
}
}
}
}

Scalable solution using CALayer in Swift

With the described InnerShadowLayer you can also enable inner shadows for specific edges only, excluding others. (e.g. you can enable inner shadows only on the left and top edges of your view)

You can then add a InnerShadowLayer to your view using:

init(...) {


// ... your initialization code ...


super.init(frame: .zero)
layer.addSublayer(shadowLayer)
}


public override func layoutSubviews() {
super.layoutSubviews()
shadowLayer.frame = bounds
}

InnerShadowLayer implementation

/// Shadow is a struct defining the different kinds of shadows
public struct Shadow {
let x: CGFloat
let y: CGFloat
let blur: CGFloat
let opacity: CGFloat
let color: UIColor
}


/// A layer that applies an inner shadow to the specified edges of either its path or its bounds
public class InnerShadowLayer: CALayer {
private let shadow: Shadow
private let edge: UIRectEdge


public init(shadow: Shadow, edge: UIRectEdge) {
self.shadow = shadow
self.edge = edge
super.init()
setupShadow()
}


required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}


public override func layoutSublayers() {
updateShadow()
}


private func setupShadow() {
shadowColor = shadow.color.cgColor
shadowOpacity = Float(shadow.opacity)
shadowRadius = shadow.blur / 2.0
masksToBounds = true
}


private func updateShadow() {
shadowOffset = {
let topWidth: CGFloat = 0
let leftWidth = edge.contains(.left) ? shadow.y / 2 : 0
let bottomWidth: CGFloat = 0
let rightWidth = edge.contains(.right) ? -shadow.y / 2 : 0


let topHeight = edge.contains(.top) ? shadow.y / 2 : 0
let leftHeight: CGFloat = 0
let bottomHeight = edge.contains(.bottom) ? -shadow.y / 2 : 0
let rightHeight: CGFloat = 0


return CGSize(width: [topWidth, leftWidth, bottomWidth, rightWidth].reduce(0, +),
height: [topHeight, leftHeight, bottomHeight, rightHeight].reduce(0, +))
}()


let insets = UIEdgeInsets(top: edge.contains(.top) ? -bounds.height : 0,
left: edge.contains(.left) ? -bounds.width : 0,
bottom: edge.contains(.bottom) ? -bounds.height : 0,
right: edge.contains(.right) ? -bounds.width : 0)
let path = UIBezierPath(rect: bounds.inset(by: insets))
let cutout = UIBezierPath(rect: bounds).reversing()
path.append(cutout)
shadowPath = path.cgPath
}
}


There's a simple solution - just draw the normal shadow and rotate, like this

@objc func shadowView() -> UIView {
let shadowView = UIView(frame: .zero)
shadowView.backgroundColor = .white
shadowView.layer.shadowColor = UIColor.grey.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0, height: 2)
shadowView.layer.shadowOpacity = 1.0
shadowView.layer.shadowRadius = 4
shadowView.layer.compositingFilter = "multiplyBlendMode"
return shadowView
}


func idtm_addBottomShadow() {
let shadow = shadowView()
shadow.transform = transform.rotated(by: 180 * CGFloat(Double.pi))
shadow.transform = transform.rotated(by: -1 * CGFloat(Double.pi))
shadow.translatesAutoresizingMaskIntoConstraints = false
addSubview(shadow)
NSLayoutConstraint.activate([
shadow.leadingAnchor.constraint(equalTo: leadingAnchor),
shadow.trailingAnchor.constraint(equalTo: trailingAnchor),
shadow.bottomAnchor.constraint(equalTo: bottomAnchor),
shadow.heightAnchor.constraint(equalToConstant: 1),
])
}