Linus G. Linus G. - 4 months ago 54
iOS Question

Draw Drop Shadow Outside UIView

Background



I have a
UIView
with the following properties:


  • alpha = 1

  • backgroundColor = white, with 0.35 opacity

  • rounded corners

  • drop shadow



Code



This is how I create the drop shadow: (
UIView
extension)

self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.darkGrayColor().CGColor
self.layer.shadowOffset = CGSizeMake(0, 5)
self.layer.shadowOpacity = 0.35
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).CGPath


Results



This results in the following:

current result

...while I do not want to see the shadow beneath the view like this:

wished result

Question



How can I draw the shadow outside the view only so it is not visible below it?

Thanks in advance!

Answer

This is perhaps slightly more complex than it would need to be, but here's one solution.

Extend UIView with the following method:

extension UIView {
    // Note: the method needs the view from which the context is taken as an argument.
    func dropShadow(superview: UIView) {
        // Get context from superview
        UIGraphicsBeginImageContext(self.bounds.size)
        superview.drawViewHierarchyInRect(CGRect(x: -self.frame.minX, y: -self.frame.minY, width: superview.bounds.width, height: superview.bounds.height), afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // Add a UIImageView with the image from the context as a subview
        let imageView = UIImageView(frame: self.bounds)
        imageView.image = image
        imageView.layer.cornerRadius = self.layer.cornerRadius
        imageView.clipsToBounds = true
        self.addSubview(imageView)

        // Bring the background color to the front, alternatively set it as UIColor(white: 1, alpha: 0.2)
        let brighter = UIView(frame: self.bounds)
        brighter.backgroundColor = self.backgroundColor ?? UIColor(white: 1, alpha: 0.2)
        brighter.layer.cornerRadius = self.layer.cornerRadius
        brighter.clipsToBounds = true
        self.addSubview(brighter)

        // Set the shadow
        self.layer.masksToBounds = false
        self.layer.shadowColor = UIColor.darkGrayColor().CGColor
        self.layer.shadowOffset = CGSizeMake(0, 5)
        self.layer.shadowOpacity = 0.35
        self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).CGPath
    }
}

Usage, considering the background view is named view:

let shadowView = UIView(frame: CGRect(x: 100, y: 100, width: 300, height: 200))
shadowView.layer.cornerRadius = 15.0
shadowView.dropShadow(view)

view.addSubview(shadowView)

Which results in a view like this:

UIView

Note: the dropShadow method can not be called from viewDidLoad as this causes issues with the graphics context. So, use this method in viewWillAppear earliest for the above result.


Here's the code for the background view, just in case somebody wants to test in playgrounds:

let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 400))
view.backgroundColor = UIColor.clearColor()

let color1 = UIColor(hue: 0.39, saturation: 0.7, brightness: 1.0, alpha: 1.0).CGColor
let color2 = UIColor(hue: 0.51, saturation: 0.9, brightness: 0.6, alpha: 1.0).CGColor

let gradient = CAGradientLayer()
gradient.frame = view.frame
gradient.colors = [color1, color2]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 1)
view.layer.insertSublayer(gradient, atIndex: 0)