Swift – Cut hole in shadow layer

Fortunately it’s now very easy today (2020)

These days it’s very easy to do this:

enter image description here

Here’s the whole thing

import UIKit
class GlowBox: UIView {
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        backgroundColor = .clear
        layer.shadowOpacity = 1
        layer.shadowColor = UIColor.red.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 0)
        layer.shadowRadius = 3
        
        let p = UIBezierPath(
             roundedRect: bounds.insetBy(dx: 0, dy: 0),
             cornerRadius: 4)
        let hole = UIBezierPath(
             roundedRect: bounds.insetBy(dx: 2, dy: 2),
             cornerRadius: 3)
             .reversing()
        p.append(hole)
        layer.shadowPath = p.cgPath
    }
}

A really handy tip:

When you add (that is .append ) two bezier paths like that…

The second one has to be either “normal” or “reversed”.

Notice the line of code near the end:

             .reversing()

Like most programmers, I can NEVER REMEMBER if it should be “normal” or “reversed” in different cases!!

The simple solution …

Very simply, try both!

Simply try it with, and without, the .reversing()

It will work one way or the other! 🙂

If you want JUST the shadow to be ONLY outside, with the insides exactly cut out:

override func layoutSubviews() {
    super.layoutSubviews()
    
    // take EXTREME CARE of frame vs. bounds
    // and + vs - throughout this function:

    let _rad: CGRect = 42
    colorAndShadow.frame = bounds
    colorAndShadow.path =
       UIBezierPath(roundedRect: bounds, cornerRadius: _rad).cgPath
    
    let enuff: CGFloat = 200
    
    shadowHole.frame = colorAndShadow.frame.insetBy(dx: -enuff, dy: -enuff)
    
    let _sb = shadowHole.bounds
    let p = UIBezierPath(rect: _sb)
    let h = UIBezierPath(
      roundedRect: _sb.insetBy(dx: enuff, dy: enuff),
      cornerRadius: _rad)
    
    p.append(h)
    shadowHole.fillRule = .evenOdd
    shadowHole.path = p.cgPath
    
    layer.mask = shadowHole
}

shadowHole is a CAShapeLayer that you must set up using a lazy variable in the usual way.

Leave a Comment