Search code examples
iosswiftuikitcalayer

In iOS, how to have view with rounded corners and drop shadow mask its subview?


I'm trying to create something like this image except for those purple corners. Notice how the blue (shown as purple) contentView is not being clipped or masked by its parent containerView.

Card view

The requirements are to have a view which:

  1. Has rounded corners.
  2. Has a shadow.
  3. It's subviews don't leak outside of it or its corners.

Here is the code I'm playing around with and I'm not sure exactly how to get this to work.

let containerView = UIView(frame: CGRect(x: 300, y: 100, width: 200, height: 200))
containerView.backgroundColor = .green
containerView.layer.cornerRadius = 40
containerView.layer.shadowRadius = 50
containerView.layer.shadowOffset = .zero
containerView.layer.shadowColor = UIColor.red.cgColor
containerView.layer.shadowOpacity = 1
view.addSubview(containerView)

let backgroundLayer = CALayer()
backgroundLayer.frame = containerView.layer.bounds
backgroundLayer.backgroundColor = UIColor.black.cgColor
backgroundLayer.opacity = 0.5
backgroundLayer.cornerRadius = containerView.layer.cornerRadius
backgroundLayer.masksToBounds = true
containerView.layer.addSublayer(backgroundLayer)

let contentView = UIView(frame: containerView.bounds)
contentView.backgroundColor = .blue
contentView.alpha = 0.3

// Omitting this line will produce a green square with rounded corners
// and a red shadow.
containerView.addSubview(contentView)

Based on this example code, one of my more specific questions is why doesn't the backgroundLayer which sets masksToBounds = true, mask the view's subview?


Solution

  • Based on this example code, one of my more specific questions is why doesn't the backgroundLayer which sets masksToBounds = true, mask the view's subview?

    Because the subview is the subview of the view, not of the backgroundLayer.

    Views are layers. Clipping is masking. A layer masks its own drawing and that of its sublayers. The subview's layer is not a sublayer of the backgroundLayer. It is its sibling, not its child. Your view/layer hierarchy looks like this:

        containerView
         |          \
      [layer]   backgroundLayer
         |         (clips itself and its sublayers,
     contentView    but it has no sublayers)
         |
      [layer]