📝 Rounded corners and drop shadows

UIViews that have rounded corners with shadows are still a popular iOS design pattern. I wanted to experiment with recreating this look so I took an evening to try to recreate it. I ended up with this little extension:

extension UIView {
    func addShadow() {
        let radiusSize = CGSize(width: self.layer.cornerRadius, 
                               height: self.layer.cornerRadius)

        let path = UIBezierPath(roundedRect: self.bounds, 
                          byRoundingCorners: .allCorners, 
                                cornerRadii: radiusSize)

        self.layer.shadowPath = path.cgPath
        self.layer.shadowRadius = 2.0
        self.layer.shadowOffset = CGSize.zero
        self.layer.shadowOpacity = 0.2
        self.layer.shadowColor = UIColor.darkGray.cgColor
    }
}

Now, to explain how it works:

Every UIView is backed by a CALayer. This extension on UIView modifies the shadow-related properties on that layer.

In this case, I set an explicit CGPath for the CALayer’s shadow path. Apple documentation explicitly says defining this value yourself is usually more effecient, otherwise UIKit has to do more work behind the scenes to figure out how to draw the shadow:

Specifying an explicit path usually improves rendering performance.

- Apple CALayer.shadowPath documentation

So, in the two lines right before I set the shadowPath, I calculate create a UIBezierPath that represents the size of the UIView’s bounds, but with rounded corners. The radius of the corners is defnined as the same size as the CALayer’s cornerRadius.

This is an assumption I’ve chosen to make - if the UIView’s CALayer already has a corner radius applied, the drop shadow will have that same corner radius too. If there isn’t a corner radius applied, the drop shadow just assumes the shape of a rectangle.

If I didn’t make this assumption and instead just let the path be UIBezierPath(rect: self.bounds), the shadow would always be in the shape of a rectangle, which wouldn’t look good if the view’s layer had a corner radius applied.

Here’s the result:

drop-shadow-uiview

And the full code for reference:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Setting the background a little darker than plain white, 
        // which is fairly popular these days.
        self.view.backgroundColor = UIColor(red: 0.96, 
                                          green: 0.96, 
                                           blue: 0.96, 
                                          alpha: 1.00)

        // Normally I would resize this view with constraints
        // but this is just a demo.
        let viewFrame = CGRect(x: 0,
                               y: 0, 
                           width: self.view.frame.width * 0.50, 
                          height: 150)

        let customView = UIView(frame: viewFrame)
        customView.center = self.view.center
        customView.backgroundColor = UIColor.white
        customView.layer.cornerRadius = 14.0
        customView.addShadow()
        self.view.addSubview(customView)
    }
}

extension UIView {
    func addShadow() {
        let radiusSize = CGSize(width: self.layer.cornerRadius, 
                               height: self.layer.cornerRadius)

        let path = UIBezierPath(roundedRect: self.bounds, 
                          byRoundingCorners: .allCorners, 
                                cornerRadii: radiusSize)

        self.layer.shadowPath = path.cgPath
        self.layer.shadowRadius = 2.0
        self.layer.shadowOffset = CGSize.zero
        self.layer.shadowOpacity = 0.2
        self.layer.shadowColor = UIColor.darkGray.cgColor
    }
}