Give your UIViews and UIButtons some rounded edges and a shadow!

Somehow, making a button rounded, and then adding a shadow to it, seems to be more difficult than it should. Here's how to do it in Swift!

Give your UIViews and UIButtons some rounded edges and a shadow!

Update: the follow-up, on how to animate our buttons when users interact with them is available here.

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

Improving Performance (Update!)

Following the publication of this post, I've been told by Julien Sagot on Twitter that shadows are really expensive and I should really look into CALayer's shadowPath. So here's the actual end result :-):

func makeRoundedAndShadowed(view: UIView) {
    let shadowLayer = CAShapeLayer()
    
    view.layer.cornerRadius = 20
    shadowLayer.path = UIBezierPath(roundedRect: view.bounds,
                                    cornerRadius: view.layer.cornerRadius).cgPath
    shadowLayer.shadowPath = shadowLayer.path
    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)
}

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:

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 ShadowableRoundableView 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
    }
}
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()
        }
    }
    
    private(set) lazy var shadowLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        self.layer.insertSublayer(layer, at: 0)
        self.setNeedsLayout()
        return layer
    }()
    
    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.

And if you want to make even better buttons, with neat animations, check out my new post!

Make your UIButtons come to life
After giving our UIViews’ both shadows AND rounded corners, today we will learn how to animate UIButtons and make them visually respond to our users’ touch!

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!

And if you want to improve your buttons with animations, you can find the follow-up here!

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