How to control shadow spread and blur?

69,955

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:

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

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

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

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.

Share:
69,955
Quantaliinuxite
Author by

Quantaliinuxite

Updated on August 17, 2021

Comments

  • Quantaliinuxite
    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:

    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
    }
    
  • Quantaliinuxite
    Quantaliinuxite over 7 years
    The information is wrong, offset is not a control of spread, it's a control of x and y.
  • O-mkar
    O-mkar over 7 years
    @Quantaliinuxite i accidentally interchanged the comments i have updated the answers
  • Quantaliinuxite
    Quantaliinuxite over 7 years
    Right, and now comes the core of my question: how do I control spread?
  • O-mkar
    O-mkar over 7 years
    @Quantaliinuxite Updated code now change 2.1 to amount of spread you need
  • Sunita
    Sunita over 7 years
    Thanks for the answer. Here I am not able to understand shadowOpacity (why its is used).
  • O-mkar
    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
    CIFilter over 6 years
    The blur value in Sketch does not match Core Animation's shadow radius.
  • CIFilter
    CIFilter over 6 years
    What ended up working close enough for us was halving the blur value in Sketch for Core Animation's shadow radius.
  • Stephan
    Stephan about 6 years
    Hm. 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
    Hashem Aboonajmi about 6 years
    @Senseful I'm interested to know how you achieved this formula?!
  • daredevil1234
    daredevil1234 about 6 years
    I'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
    ScottyBlades over 5 years
    You forgot to set maskToBounds = false.
  • matchifang
    matchifang over 5 years
    How do you know that blur = 4 translates to shadowRadius 4 / 2.0 ? I would like to understand how they are related. Thank you.
  • José
    José over 5 years
    As mentioned above by ScottyBlades, this won't work without maskToBounds = false. An example where it will fail is when applying this shadow to an UIButton.
  • CIFilter
    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
    Vyachaslav Gerchicov about 5 years
    @O-mkar your answer still contains errors about spread. Fix it please
  • JWK
    JWK almost 5 years
    Should this be shadowRadius = blur / UIScreen.main.scale as opposed to hard-coding blur / 2.0? Or, is there another reason to divide by 2? @matchifang Did you figure this out?
  • Jan
    Jan almost 5 years
    This "almost" works but the spread is wrong when you use a spread of 1 with x and y 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
    Jens Schwarzer over 4 years
    Thanks! Maybe for better clarification x/y/blur should be named x/y/blurInPoints or x/y/blurInPixels, whatever is correct :)
  • mamba4ever
    mamba4ever about 4 years
    Shouldn't you return layer.shadowRadius * 2 for shadowBlur getter
  • Dave Y
    Dave Y almost 4 years
    This is not an answer - comments go in comments!
  • Vyachaslav Gerchicov
    Vyachaslav Gerchicov over 3 years
    spread and offset are different params. Don't get people confused!
  • eli7ah
    eli7ah over 3 years
    Great simple and user-friendly solution! Thanx for all who participated in!
  • Cristian Gomez
    Cristian Gomez over 3 years
    What's dx? I'm not seeing it defined anywhere
  • midnight
    midnight almost 3 years
    yo why you multiply or divide shadow radius? can someone please explain