Match a view's shadow to the Sketch shadow

UIKit Sketch Shadow

If you have ever been working from a Sketch design, a shadow might be one of the most frustrating elements to work with. That's because it has a property that doesn't exist in CALayer's shadow, spread. And even the one that looks like the same property, blur and shadowRadius, doesn't produce the same effect.

Sketch shadow property
Sketch shadow property

The following are the result of setting a shadow offset of (20, 20) and a blur radius of 20 in iOS and Sketch.

view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 20, height: 20)
view.layer.shadowOpacity = 1
view.layer.shadowRadius = 20

And here is our setting in Sketch.

Sketch shadow setting
Sketch shadow setting

As you can see, the result is not the same.

iOS shadow
iOS shadow with offset of (20, 20) and shadowRadius of 20
Sketch shadow
Sketch shadow with offset of (20, 20) and blur of 20

Blur vs. Shadow Radius #

From the above example, the offset seems to be right, but blur effect obviously not. The shadow in iOS is more blurry than the one in Sketch, so we need to tune it down a bit. To do that, we have to divide a value that we get from the Sketch with some value before we can use it as shadowRadius. From what I read and try, the magic number is 2.

view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 20, height: 20)
view.layer.shadowOpacity = 1
view.layer.shadowRadius = 20/2
iOS shadow
iOS shadow with offset of (20, 20) and shadowRadius of 10

The following is the one from Sketch for comparison.

Sketch shadow
Sketch shadow with offset of (20, 20) and blur of 20

It might not be identical, but it good enough, and number two is easy to remember. We can match the blur value; the next one is the spread radius (spread).

Spread #

Spread radius increase / decrease the size of the shadow. A positive value increases the size of the shadow, and a negative value decreases the size of the shadow.

You can think of this as an inset of a shadow. The best way to understand this is setting shadow-offset to (0, 0) and blur radius to 0, then play around with spread.

Setting offset to (0, 0) and blur to 0 to make a shadow an identical size as the square. Then we set the spread value to 0, 20, and 40. As you can see, the shadow increases its size in all directions equal to the value specified in spread, so it looks like a border for our square view.

Sketch shadow with spread of 0, 20, and 40 respectively
Sketch shadow with spread of 0, 20, and 40 respectively

To replicate this behavior in iOS, we use insetBy(dx:dy:) and shadowPath.

Here is a shadow with an offset of (20, 20), a blur of 20, and a spread of 20 in Sketch.

Sketch shadow with an offset of (20, 20), a blur of 20, and a spread of 20
Sketch shadow with an offset of (20, 20), a blur of 20, and a spread of 20

We use insetBy(dx:dy:) and shadowPath to achieve the same effect.

view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 20, height: 20)
view.layer.shadowOpacity = 1
view.layer.shadowRadius = 20/2

let spread: CGFloat = 20 // <1>
let rect = view.bounds.insetBy(dx: -dx, dy: -dx) // <2>
view.layer.shadowPath = UIBezierPath(rect: rect).cgPath

<1> 20 is our spread value from Sketch.
<2> In sketch, positive value of spread in crease the size of the shadow, but positive parameter in insetBy(dx:dy:) will use as an inset which will reduce the rectangle size. So, we need to invert the sign to make the result rectangle bigger.

iOS shadow with an offset of (20, 20), a blur of 20, and a spread of 20
iOS shadow with an offset of (20, 20), a blur of 20, and a spread of 20

For comparison, this is the one from Sketch.

Sketch shadow with an offset of (20, 20), a blur of 20, and a spread of 20
Sketch shadow with an offset of (20, 20), a blur of 20, and a spread of 20

Extension #

We can create an extension for this conversion.

extension UIView {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.2,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0)
{
layer.shadowColor = color.cgColor
layer.shadowOpacity = alpha
layer.shadowOffset = CGSize(width: x, height: y)
layer.shadowRadius = blur / 2
if spread == 0 {
layer.shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy this article, you can subscribe to the weekly newsletter.

Every Friday, you’ll get a quick recap of all articles and tips posted on this site — entirely for free.

← Home