UIView with rounded corners and drop shadow?
Swift
// corner radius
blueView.layer.cornerRadius = 10
// border
blueView.layer.borderWidth = 1.0
blueView.layer.borderColor = UIColor.black.cgColor
// shadow
blueView.layer.shadowColor = UIColor.black.cgColor
blueView.layer.shadowOffset = CGSize(width: 3, height: 3)
blueView.layer.shadowOpacity = 0.7
blueView.layer.shadowRadius = 4.0
Exploring the options
Problem 1: Shadow gets clipped off
What if there are sublayers or subviews (like an image) whose content we want to clip to the bounds of our view?
We can accomplish this with
blueView.layer.masksToBounds = true
(Alternatively, blueView.clipsToBounds = true
gives the same result.)
But, oh no! The shadow was also clipped off because it's outside of the bounds! What to do? What to do?
Solution
Use separate views for the shadow and the border. The base view is transparent and has the shadow. The border view clips any other subcontent that it has to its borders.
// add the shadow to the base view
baseView.backgroundColor = UIColor.clear
baseView.layer.shadowColor = UIColor.black.cgColor
baseView.layer.shadowOffset = CGSize(width: 3, height: 3)
baseView.layer.shadowOpacity = 0.7
baseView.layer.shadowRadius = 4.0
// add the border to subview
let borderView = UIView()
borderView.frame = baseView.bounds
borderView.layer.cornerRadius = 10
borderView.layer.borderColor = UIColor.black.cgColor
borderView.layer.borderWidth = 1.0
borderView.layer.masksToBounds = true
baseView.addSubview(borderView)
// add any other subcontent that you want clipped
let otherSubContent = UIImageView()
otherSubContent.image = UIImage(named: "lion")
otherSubContent.frame = borderView.bounds
borderView.addSubview(otherSubContent)
This gives the following result:
Problem 2: Poor performance
Adding rounded corners and shadows can be a performance hit. You can improve performance by using a predefined path for the shadow and also specifying that it be rasterized. The following code can be added to the example above.
baseView.layer.shadowPath = UIBezierPath(roundedRect: baseView.bounds, cornerRadius: 10).cgPath
baseView.layer.shouldRasterize = true
baseView.layer.rasterizationScale = UIScreen.main.scale
See this post for more details. See here and here also.
This answer was tested with Swift 4 and Xcode 9.
The following code snippet adds a border, border radius, and drop shadow to v
, a UIView
:
// border radius
[v.layer setCornerRadius:30.0f];
// border
[v.layer setBorderColor:[UIColor lightGrayColor].CGColor];
[v.layer setBorderWidth:1.5f];
// drop shadow
[v.layer setShadowColor:[UIColor blackColor].CGColor];
[v.layer setShadowOpacity:0.8];
[v.layer setShadowRadius:3.0];
[v.layer setShadowOffset:CGSizeMake(2.0, 2.0)];
Swift 5 Version :
// border radius
v.layer.cornerRadius = 30.0
// border
v.layer.borderColor = UIColor.lightGray.cgColor
v.layer.borderWidth = 1.5
// drop shadow
v.layer.shadowColor = UIColor.black.cgColor
v.layer.shadowOpacity = 0.8
v.layer.shadowRadius = 3.0
v.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
You can adjust the settings to suit your needs.
Also, add the QuartzCore framework to your project and:
#import <QuartzCore/QuartzCore.h>
See my other answer regarding masksToBounds
.
Note
This may not work in all cases. If you find that this method interferes with other drawing operations that you are performing, please see this answer.
Check out the example project on GitHub to make sure you use the component correctly.
Simple Swift 5 solution without any additional subviews or subclassing:
extension UIView {
func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float) {
layer.masksToBounds = false
layer.shadowOffset = offset
layer.shadowColor = color.cgColor
layer.shadowRadius = radius
layer.shadowOpacity = opacity
let backgroundCGColor = backgroundColor?.cgColor
backgroundColor = nil
layer.backgroundColor = backgroundCGColor
}
}
Note that you should set up your view with corner radius and other properties before calling addShadow
.
After that, just call this from viewDidLoad
like this:
button.addShadow(offset: CGSize.init(width: 0, height: 3), color: UIColor.black, radius: 2.0, opacity: 0.35)
Final result:
Super easy and simple!