I am trying to add a shadow on my view. It is having 3 rounded edges. To achieve this UIBezierPath and then setting the CAShapelayer with this path as the mask on the layer of the view. Now, if I'm trying to add shadow on this view, it is not showing. I have gone through a similar question and suggested answers but nothing works in my case. Following is my implementation:
class BubbleView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
}
override func layoutSubviews() {
super.layoutSubviews()
self.updateContainerLayer()
}
func updateContainerLayer() {
let brazierPath: UIBezierPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.bottomRight, .bottomLeft, .topLeft],
cornerRadii: CGSize(width: 15.0, height: 0.0))
//1
let shapeLayer = CAShapeLayer()
shapeLayer.path = brazierPath.cgPath
self.layer.mask = shapeLayer
//2
self.layer.shadowColor = UIColor(r: 0, g: 0, b: 0, alpha: 0.25).cgColor
self.layer.shadowOpacity = 1.0
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.5)
self.layer.shadowRadius = 1.5
self.layer.shadowPath = brazierPath.cgPath
//3
self.layer.masksToBounds = true
self.clipsToBounds = false
//4
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
If you are targeting iOS 11+ you can use the layer's .maskedCorners
property:
class BubbleView: UIView {
// don't override draw()
// override func draw(_ rect: CGRect) {
// super.draw(rect)
// }
override func layoutSubviews() {
super.layoutSubviews()
self.updateContainerLayer()
}
func updateContainerLayer() {
let brazierPath: UIBezierPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.bottomRight, .bottomLeft, .topLeft],
cornerRadii: CGSize(width: 15.0, height: 0.0))
//1
// let shapeLayer = CAShapeLayer()
// shapeLayer.path = brazierPath.cgPath
// self.layer.mask = shapeLayer
// iOS 11+ use .maskedCorners
self.layer.cornerRadius = 15.0
self.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
//2
self.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
self.layer.shadowOpacity = 1.0
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.5)
self.layer.shadowRadius = 1.5
self.layer.shadowPath = brazierPath.cgPath
//3
self.layer.masksToBounds = true
self.clipsToBounds = false
//4
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
Result:
Result with an exaggerated .shadowOffset = CGSize(width: -10.0, height: 10.5)
to make it easy to see the shadow:
If you need to allow earlier iOS versions, I believe you'll need to use a container view approach.
EDIT:
Another approach, using a "container" view for the shadow. This will work with iOS earlier than 11... it uses the same UIBezierPath
for the "content view" mask and the shadow path:
class BubbleView: UIView {
let contentView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
addSubview(contentView)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
])
self.clipsToBounds = false
backgroundColor = .clear
contentView.backgroundColor = .red
// set non-changing properties here
contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
self.layer.shadowOpacity = 1.0
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.5)
// exaggerated shadow offset so we can see it easily
//self.layer.shadowOffset = CGSize(width: -10.0, height: 10.5)
self.layer.shadowRadius = 1.5
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
override func layoutSubviews() {
super.layoutSubviews()
let bezierPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.bottomRight, .bottomLeft, .topLeft],
cornerRadii: CGSize(width: 15.0, height: 0.0))
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
contentView.layer.mask = shapeLayer
self.layer.shadowPath = bezierPath.cgPath
}
}
As with the 11+ example, Result:
and Result with an exaggerated .shadowOffset = CGSize(width: -10.0, height: 10.5)
to make it easy to see the shadow: