How to control shadow spread and blur?
IosSwiftUiviewCore GraphicsIos Problem Overview
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
}
Ios Solutions
Solution 1 - Ios
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 - Ios
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 - Ios
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 - Ios
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 - Ios
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.
Solution 6 - Ios
My solution based on this post replies: (Swift 3)
let shadowPath = UIBezierPath(rect: CGRect(x: -1,
y: -2,
width: target.frame.width + 2,
height: target.frame.height + 2))
target.layer.shadowColor = UIColor(hexString: shadowColor).cgColor
target.layer.shadowOffset = CGSize(width: CGFloat(shadowOffsetX), height: CGFloat(shadowOffsetY))
target.layer.masksToBounds = false
target.layer.shadowOpacity = Float(shadowOpacity)
target.layer.shadowPath = shadowPath.cgPath
Solution 7 - Ios
Change a little bit from @Senseful 's answer and works fine to my project.
- Support shadow corner radius (for some round corner view)
spread == 0
it still apply shadow effect(looks like border)
struct SketchShadow {
var color: UIColor = .black
let alpha: Float = 0.1
let x: CGFloat
let y: CGFloat
let blur: CGFloat
let spread: CGFloat
let cornorRadius: CGFloat
func applyToLayer(_ layer: CALayer) {
layer.masksToBounds = false
layer.shadowColor = color.cgColor
layer.shadowOpacity = alpha
layer.shadowOffset = CGSize(width: x, height: y)
layer.shadowRadius = blur / 2.0
if spread == 0 {
layer.shadowPath = UIBezierPath(roundedRect: layer.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornorRadius, height: cornorRadius)).cgPath
} else {
let dx = -(spread)
let rect = layer.bounds.insetBy(dx: dx, dy: dx)
layer.shadowPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornorRadius, height: cornorRadius)).cgPath
}
}
}
Solution 8 - Ios
I really like the answer posted here and suggestions in the comments. This is how I modified that solution:
extension UIView {
func applyShadow(color: UIColor, alpha: Float, x: CGFloat, y: CGFloat, blur: CGFloat, spread: CGFloat) {
layer.masksToBounds = false
layer.shadowColor = color.cgColor
layer.shadowOpacity = alpha
layer.shadowOffset = CGSize(width: x, height: y)
layer.shadowRadius = blur / UIScreen.main.scale
if spread == 0 {
layer.shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
USAGE:
myButton.applyShadow(color: .black, alpha: 0.2, x: 0, y: 1, blur: 2, spread: 0)
Solution 9 - Ios
It might be a little diggin in history, but maybe some had same issue. I useed code sample from accepted answer. However the effects are quite different:
- y value has to be around half compared to same value in sketch
- I tried to apply shadow on navigation bar and the effect is terribly different - barely visible while using same values that sketch had.
So there seems that the method is totally not reflecting sketch parameters. Any hints?