How to control shadow spread and blur?
Solution 1
Here's how to apply all 6 Sketch shadow properties to a UIView's layer with near perfect accuracy:
extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0)
{
masksToBounds = false
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
Say we want to represent the following:
You can easily do this via:
myView.layer.applySketchShadow(
color: .black,
alpha: 0.5,
x: 0,
y: 0,
blur: 4,
spread: 0)
or more succinctly:
myView.layer.applySketchShadow(y: 0)
Example:
Left: iPhone 8 UIView screenshot; right: Sketch rectangle.
Note:
- When using a non-zero
spread
, it hardcodes a path based on thebounds
of the CALayer. If the layer's bounds ever change, you'd want to call theapplySketchShadow()
method again.
Solution 2
You can try this .... you can play with the values.
The shadowRadius
dictates the amount of blur. shadowOffset
dictates where the shadow goes.
Swift 2.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.CGPath
Swift 3.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.cgPath
Example with spread
To create a basic shadow
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
Basic Shadow example in Swift 2.0
Solution 3
Sketch Shadow Using IBDesignable and IBInspectable in Swift 4
SKETCH AND XCODE SIDE BY SIDE
CODE
@IBDesignable class ShadowView: UIView {
@IBInspectable var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
@IBInspectable var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
@IBInspectable var shadowOffset: CGPoint {
get {
return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
}
set {
layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
}
}
@IBInspectable var shadowBlur: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue / 2.0
}
}
@IBInspectable var shadowSpread: CGFloat = 0 {
didSet {
if shadowSpread == 0 {
layer.shadowPath = nil
} else {
let dx = -shadowSpread
let rect = bounds.insetBy(dx: dx, dy: dx)
layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
}
OUTPUT
HOW TO USE IT
Solution 4
This code worked very well for me:
yourView.layer.shadowOpacity = 0.2 // opacity, 20%
yourView.layer.shadowColor = UIColor.black.cgColor
yourView.layer.shadowRadius = 2 // HALF of blur
yourView.layer.shadowOffset = CGSize(width: 0, height: 2) // Spread x, y
yourView.layer.masksToBounds = false
Solution 5
For those who are attempting to apply a shadow to a predefined path (Like for a circular view, for instance), here's what I ended up with:
extension CALayer {
func applyShadow(color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0,
path: UIBezierPath? = nil) {
shadowColor = color.cgColor
shadowOpacity = alpha
shadowRadius = blur / 2
if let path = path {
if spread == 0 {
shadowOffset = CGSize(width: x, height: y)
} else {
let scaleX = (path.bounds.width + (spread * 2)) / path.bounds.width
let scaleY = (path.bounds.height + (spread * 2)) / path.bounds.height
path.apply(CGAffineTransform(translationX: x + -spread, y: y + -spread).scaledBy(x: scaleX, y: scaleY))
shadowPath = path.cgPath
}
} else {
shadowOffset = CGSize(width: x, height: y)
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
shouldRasterize = true
rasterizationScale = UIScreen.main.scale
}
}
I'll post some examples later, but this has worked spot on for circular views for me.
Quantaliinuxite
Updated on August 17, 2021Comments
-
Quantaliinuxite over 2 years
I have designed UI elements in sketch, and one of them has a shadow with blur 1 and spread 0. I looked at the doc for the views layer property and layer doesn't have anything named spread or blur, or anything equivalent (the only control was merely shadowOpacity). How can control things like blur and spread?
Here are my settings in Sketch:
And here is what I want my shadow to look like:
And here is what it looks like at the moment:
Note, you have to click on the picture to actually see the shadow.
My code is as follows:
func setupLayer(){ view.layer.cornerRadius = 2 view.layer.shadowColor = Colors.Shadow.CGColor view.layer.shadowOffset = CGSize(width: 0, height: 1) view.layer.shadowOpacity = 0.9 view.layer.shadowRadius = 5 }
-
Quantaliinuxite over 7 yearsThe information is wrong, offset is not a control of spread, it's a control of x and y.
-
O-mkar over 7 years@Quantaliinuxite i accidentally interchanged the comments i have updated the answers
-
Quantaliinuxite over 7 yearsRight, and now comes the core of my question: how do I control spread?
-
O-mkar over 7 years@Quantaliinuxite Updated code now change 2.1 to amount of spread you need
-
Sunita over 7 yearsThanks for the answer. Here I am not able to understand shadowOpacity (why its is used).
-
O-mkar over 7 years@Sunita shadowOpacity sets how transparent the shadow is, where 0 is invisible and 1 is as strong as possible.
-
CIFilter over 6 yearsThe blur value in Sketch does not match Core Animation's shadow radius.
-
CIFilter over 6 yearsWhat ended up working close enough for us was halving the blur value in Sketch for Core Animation's shadow radius.
-
Stephan about 6 yearsHm. In Sketch, if you apply spread, it 'moves out', where the blur/gradient of the shadow begins. Which means - it stays at the constant shadow-colour, until the blur begins. But if we move the shadowLayer around, the shadow would only begin from that layer, right? (Say spread is 10, the shadow would only begin after an offset of 10, where as in Sketch, the blur/gradient would begin after an offset of 10). So they don't really match up, right?
-
Hashem Aboonajmi about 6 years@Senseful I'm interested to know how you achieved this formula?!
-
daredevil1234 about 6 yearsI'm not sure this should be the accepted answer. I testing this out, and there seem to be some cases where the shadows apply to the subviews instead of the view I actually add it to
-
ScottyBlades over 5 yearsYou forgot to set
maskToBounds = false
. -
matchifang over 5 yearsHow do you know that blur = 4 translates to shadowRadius 4 / 2.0 ? I would like to understand how they are related. Thank you.
-
José over 5 yearsAs mentioned above by ScottyBlades, this won't work without
maskToBounds = false
. An example where it will fail is when applying this shadow to anUIButton
. -
CIFilter about 5 years@daredevil1234
CALayer
shadows will always be applied to rasterized layer contents, which includes all sublayers. If you want to exclude sublayers, you need to move the layer you want to apply a shadow to up in the hierarchy so it has no children and only has sibling layers. So it's more about how you structure your layers than it is an incorrect solution. -
Vyachaslav Gerchicov about 5 years@O-mkar your answer still contains errors about spread. Fix it please
-
JWK almost 5 yearsShould this be
shadowRadius = blur / UIScreen.main.scale
as opposed to hard-codingblur / 2.0
? Or, is there another reason to divide by 2? @matchifang Did you figure this out? -
Jan almost 5 yearsThis "almost" works but the spread is wrong when you use a spread of
1
withx
andy
0
shapeLayer.applyShadow(color: .black, alpha: 1, x: 0, y: 0, blur: 0, spread: 1, path: path)
. In this case the shadow has an offset while it should not. Here the triangle should have the shadow spread 1px in all directions ibb.co/YjwRb9B -
Jens Schwarzer over 4 yearsThanks! Maybe for better clarification x/y/blur should be named x/y/blurInPoints or x/y/blurInPixels, whatever is correct :)
-
mamba4ever about 4 yearsShouldn't you return
layer.shadowRadius * 2
for shadowBlur getter -
Dave Y almost 4 yearsThis is not an answer - comments go in comments!
-
Vyachaslav Gerchicov over 3 yearsspread and offset are different params. Don't get people confused!
-
eli7ah over 3 yearsGreat simple and user-friendly solution! Thanx for all who participated in!
-
Cristian Gomez over 3 yearsWhat's dx? I'm not seeing it defined anywhere
-
midnight almost 3 yearsyo why you multiply or divide shadow radius? can someone please explain