How to control shadow spread and blur?

IosSwiftUiviewCore Graphics

Ios 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:

Sketch shadow settings

And here is what I want my shadow to look like:

Shadow wanted

And here is what it looks like at the moment:

Current shadow

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:

enter image description here

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:

> enter image description here > > Left: iPhone 8 UIView screenshot; right: Sketch rectangle.

Note:

  • When using a non-zero spread, it hardcodes a path based on the bounds of the CALayer. If the layer's bounds ever change, you'd want to call the applySketchShadow() 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

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

OUTPUT

Solution 3 - Ios

Sketch Shadow Using IBDesignable and IBInspectable in Swift 4

> SKETCH AND XCODE SIDE BY SIDE

Shadow Exampl

> 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

DEMO OUTPUT

> HOW TO USE IT

DEMO

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?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionQuantaliinuxiteView Question on Stackoverflow
Solution 1 - IosSensefulView Answer on Stackoverflow
Solution 2 - IosO-mkarView Answer on Stackoverflow
Solution 3 - IosO-mkarView Answer on Stackoverflow
Solution 4 - IosCesareView Answer on Stackoverflow
Solution 5 - IosZigView Answer on Stackoverflow
Solution 6 - IosJosep EscobarView Answer on Stackoverflow
Solution 7 - IosRogerView Answer on Stackoverflow
Solution 8 - IosbudiDinoView Answer on Stackoverflow
Solution 9 - IosPiotr GawłowskiView Answer on Stackoverflow