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?
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!
Shadows really are a great way to bring our UI to life with some depth - Photo by Andreas Weiland / Unsplash 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 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!
Comments ()