This sounds simple, right? All we have to do is...

func makeRoundedAndShadowed(view: UIView) {
	view.layer.cornerRadius = 20
}
... add a corner radius to our view..
func makeRoundedAndShadowed(view: UIView) {
	view.layer.cornerRadius = 20
	view.layer.masksToBounds = true
}
... make the layer mask to it's bounds...
func makeRoundedAndShadowed(view: UIView) {
	view.layer.cornerRadius = 20
	view.layer.masksToBounds = true

	view.layer.shadowColor = UIColor.darkGray.cgColor
}
... set our button's layer's shadow's color...
func makeRoundedAndShadowed(view: UIView) {
	view.layer.cornerRadius = 20
	view.layer.masksToBounds = true

	view.layer.shadowColor = UIColor.darkGray.cgColor
	view.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
}
... it's offset...
func makeRoundedAndShadowed(view: UIView) {
	view.layer.cornerRadius = 20
	view.layer.masksToBounds = true

	view.layer.shadowColor = UIColor.darkGray.cgColor
	view.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
	view.layer.shadowOpacity = 0.4
}
... opacity...
func makeRoundedAndShadowed(view: UIView) {
	view.layer.cornerRadius = 20
	view.layer.masksToBounds = true

	view.layer.shadowColor = UIColor.darkGray.cgColor
	view.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
	view.layer.shadowOpacity = 0.4
	view.layer.shadowRadius = 5.0
}
... and radius (size)...

... aaand we're done!

Our UIView does have rounded corners

...

Or not. Where's our damn shadow?

“The more that you read, the more things you will know. The more that you learn, the more places you’ll go.”
― Dr. Seuss
Where's our shadow, little one? - Photo by Ben White / Unsplash

The problem

The shadow is clipped. It's clipped because our layer "masksToBounds". But we need that for our view to have these rounded edges, right? What can we do? There has to be a way... right?

We're not the first ones to face this. Looking it up on Google, it appears to be quite an old question, and one that just keeps coming up. As you can see below:

Round buttons with shadow
Almost in every project I need a view or a button that has rounded corners and sometimes even a bit of shadow at the same time. It can be a bit hard when you start to apply both to the same view…
Swift Tip: Adding Rounded Corners and Shadows to a UIView
I’ve been working on an application for a couple of years and received a simple design request: Round the corners on a view and add a drop shadow. Easy right? WRONG! If you’ve tried this before…

A (working) solution

If you look at some of them, you'll find a (working) solution. Using a second view, or a layer, depending on who you ask. In our case, we won't be making yet another view. Every problem isn't a nail, and an UIView a hammer isn't all we have in our toolkit. We'll use a layer. A CALayer (here's a nice short post on the difference between those two).

So we...

func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
}
... create a new layer...
func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
    
    view.layer.cornerRadius = 20
}
... set our view's cornerRadius...
func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
    
    view.layer.cornerRadius = 20
    shadowLayer.path = UIBezierPath(roundedRect: view.bounds,
                                    cornerRadius: view.layer.cornerRadius).cgPath
}
... set the new layer's path to match our view's...
func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
    
    view.layer.cornerRadius = 20
    shadowLayer.path = UIBezierPath(roundedRect: view.bounds,
                                    cornerRadius: view.layer.cornerRadius).cgPath
    shadowLayer.fillColor = view.backgroundColor?.cgColor
}
... set the layer's fill color...
func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
    
    view.layer.cornerRadius = view.frame.height / 2
    shadowLayer.path = UIBezierPath(roundedRect: view.bounds,
                                    cornerRadius: view.layer.cornerRadius).cgPath
    shadowLayer.fillColor = view.backgroundColor?.cgColor
    shadowLayer.shadowColor = UIColor.darkGray.cgColor
}
... and it's shadow's color...
func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
    
    view.layer.cornerRadius = 20
    shadowLayer.path = UIBezierPath(roundedRect: view.bounds,
                                    cornerRadius: view.layer.cornerRadius).cgPath
    shadowLayer.fillColor = view.backgroundColor?.cgColor
    shadowLayer.shadowColor = UIColor.darkGray.cgColor
    shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
    shadowLayer.shadowOpacity = 0.4
    shadowLayer.shadowRadius = 5.0
}
... as well as it's shadow's offset, opacity and radius...
func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
    
    view.layer.cornerRadius = 20
    shadowLayer.path = UIBezierPath(roundedRect: view.bounds,
                                    cornerRadius: view.layer.cornerRadius).cgPath
    shadowLayer.fillColor = view.backgroundColor?.cgColor
    shadowLayer.shadowColor = UIColor.darkGray.cgColor
    shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
    shadowLayer.shadowOpacity = 0.4
    shadowLayer.shadowRadius = 5.0
	view.layer.insertSublayer(shadowLayer, at: 0)
}
... and insert it into our view's layer!c

And low and behold... rounded corners and a shadow!

Balloons on a string
Shadows really are a great way to bring our UI to life with some depth - Photo by Andreas Weiland / Unsplash

Reusability

Now, putting this function in our UIViewController isn't reusable. So let's do ourselves one better. No, not by using an UIView extension, because those can't hold stored properties and do tend to lead to messier code. No; we'll use a protocol, and classes!

Here's the protocol:

import UIKit

protocol ShadowableRoundableView {
  
  var cornerRadius: CGFloat { get set }
  var shadowColor: UIColor { get set }
  var shadowOffsetWidth: CGFloat { get set }
  var shadowOffsetHeight: CGFloat { get set }
  var shadowOpacity: Float { get set }
  var shadowRadius: CGFloat { get set }
  
  var shadowLayer: CAShapeLayer { get }

  func setCornerRadiusAndShadow()
}

extension ShadowedRoundedView where Self: UIView {
  func setCornerRadiusAndShadow() {
    layer.cornerRadius = cornerRadius
    shadowLayer.path = UIBezierPath(roundedRect: bounds,
                                    cornerRadius: cornerRadius).cgPath
    shadowLayer.fillColor = backgroundColor?.cgColor
    shadowLayer.shadowColor = shadowColor.cgColor
    shadowLayer.shadowPath = shadowLayer.path
    shadowLayer.shadowOffset = CGSize(width: shadowOffsetWidth,
                                      height: shadowOffsetHeight)
    shadowLayer.shadowOpacity = shadowOpacity
    shadowLayer.shadowRadius = shadowRadius
    layer.insertSublayer(shadowLayer, at: 0)
  }
}
An awesome, badly but explicitely named protocol

And here's our @IBDesignable custom UIView!


import UIKit

@IBDesignable
class OurCustomView: UIView, ShadowableRoundableView {
  
  @IBInspectable var cornerRadius: CGFloat = 0 {
    didSet {
      self.setNeedsLayout()
    }
  }

  @IBInspectable var shadowColor: UIColor = UIColor.darkGray {
    didSet {
      self.setNeedsLayout()
    }
  }

  @IBInspectable var shadowOffsetWidth: CGFloat = 3 {
    didSet {
      self.setNeedsLayout()
    }
  }


  @IBInspectable var shadowOffsetHeight: CGFloat = 3 {
    didSet {
      self.setNeedsLayout()
    }
  }


  @IBInspectable var shadowOpacity: Float = 0.4 {
    didSet {
      self.setNeedsLayout()
    }
  }


  @IBInspectable var shadowRadius: CGFloat = 4 {
    didSet {
      self.setNeedsLayout()
    }
  }

  var shadowLayer: CAShapeLayer = CAShapeLayer() {
    didSet {
      self.setNeedsLayout()
    }
  }
  
  override func layoutSubviews() {
    super.layoutSubviews()
    self.setCornerRadiusAndShadow()
  }
}
Our custom @IBDesignable view, implementing our protocol!

If you need to make it a button, just replace UIView with UIButton and you'll be set. Wish we could have done more with the protocol, but that's the way it seems to be.

Epilogue

Mia wants treats!
Well done! - Photo by Camylla Battani / Unsplash

If you have any suggestion on how to improve on this, or any question, do let me know on Twitter! I'll gladly improve upon it, and do my best to answer any and all questions. I also want to thank all those who did reach out with some questions and suggestions. I really appreciated it and am glad I could help!

That being said, thank you for reading, and have a great, beautiful day!