How to apply shadow to a round cornered UIView with subviews contrainted to edges

Written on May 22, 2020

I was googling how to apply shadow to a round cornered UIView, and
inspired from this post.

And below is my version RoundShadowView so that you can create different RoundShadowViews with size of corner radius, shadow colour and background colour.

Furthermore, since the requirement is different from the original post, I have also improved the solution from the original post, so now when you add a subview constrained to the edges, it will still works well. The original solution won’t fit the subview bounds into the rounded corner.

Instead of adding subviews to RoundShadowView, we add subviews to RoundShadowView.containerView.

If there are better solutions, please get in touch, thank you for reading!

class RoundShadowView: UIView {
    private var shadowLayer: CAShapeLayer!
    private var cornerRadius: CGFloat = 25.0
    private var fillColor: UIColor = .blue // the color applied to the shadowLayer, rather than the view's backgroundColor
    private var shadowColor: UIColor = .black

    let containerView = UIView()
    
    init(frame: CGRect, cornerRadius: CGFloat, shadowColor: UIColor, backgroundColor: UIColor = UIColor.white) {
        super.init(frame: .zero)
        self.cornerRadius = cornerRadius
        self.fillColor = backgroundColor
        self.shadowColor = shadowColor
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        containerView.translatesAutoresizingMaskIntoConstraints = false
        // set the cornerRadius of the containerView's layer
        containerView.layer.cornerRadius = cornerRadius
        containerView.layer.masksToBounds = true
        
        addSubview(containerView)
        
        NSLayoutConstraint.activate([
            containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
            containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
            containerView.topAnchor.constraint(equalTo: topAnchor),
            containerView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
        
        if shadowLayer != nil {
            if shadowLayer.frame != frame {
                shadowLayer.removeFromSuperlayer()
                shadowLayer = nil
            }
        }
        if shadowLayer == nil {
            shadowLayer = CAShapeLayer()
            
            shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
            shadowLayer.fillColor = fillColor.cgColor
            
            shadowLayer.shadowColor = shadowColor.cgColor
            shadowLayer.shadowPath = shadowLayer.path
            shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0)
            shadowLayer.shadowOpacity = 0.3
            shadowLayer.shadowRadius = 3
            
            layer.insertSublayer(shadowLayer, at: 0)
        }
    }
}