3

I want to "cut a hole" in the shadow layer of a UIView an Swift3, iOS

I have a container (UIView), that has 2 children:

  • one UIImageView
  • one UIView on top of that image ("overlay")

I want to give the overlay a shadow and cut out an inner rect of that shadow, to create a glow-like effect at the edges of the ImageView
It is crucial that the glow is inset, since the image is taking the screen width
My code so far:

let glowView = UIView(frame: CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight))
glowView.layer.shadowPath = UIBezierPath(roundedRect: container.bounds, cornerRadius: 4.0).cgPath
glowView.layer.shouldRasterize = true
glowView.layer.rasterizationScale = UIScreen.main.scale
glowView.layer.shadowOffset = CGSize(width: 1.0, height: 1.0)
glowView.layer.shadowOpacity = 0.4

container.addSubview(imageView)
container.addSubview(glowView)

The result looks like the following right now:

image

Now I would like to cut out the darker inner part, so that just the shadow at the edges remains
Any idea how to achieve this?

skaldesh
  • 1,325
  • 3
  • 15
  • 37

3 Answers3

6

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.

Fattie
  • 27,874
  • 70
  • 431
  • 719
1

Try using this as as your shadow path:

let shadowWidth = 2.0 // Do this as wide as you want
var outterPath = UIBezierPath()
outterPath.move(to: CGPoint(x: shadowWidth, y: 0))
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width, y: 0))
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width, y: glowView.bounds.size.height))
outterPath.addLine(to: CGPoint(x: 0.0, y: glowView.bounds.size.height))
outterPath.addLine(to: CGPoint(x: 0.0, y: 0.0))
outterPath.addLine(to: CGPoint(x: shadowWidth, y: 0.0))
outterPath.addLine(to: CGPoint(x: shadowWidth, y: glowView.bounds.size.height - shadowWidth))
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width - shadowWidth, y: glowView.bounds.size.height - shadowWidth))
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width - shadowWidth, y: shadowWidth))
outterPath.addLine(to: CGPoint(x: shadowWidth, y: shadowWidth))
outterPath.close()

This won't create a rounded rect but with a little bit of changes to the code above you should be able to add those too.

Mihai Fratu
  • 7,579
  • 2
  • 37
  • 63
  • 1
    Thanks, that was a very good foundation for my approach! I will make an answer and reference your answer there! – skaldesh Apr 10 '17 at 08:02
  • 1
    anyone googling here, there is a tremendously easier way to do this (also with the correct corners etc.), I typed in an answer – Fattie Nov 28 '19 at 16:16
-3

Thanks to Mihai Fratu's answer I was able to create a UIBezierPath that exactly meets my needs
I post my code here in case somebody has the same problem later

let innerRadius: CGFloat = 32.0 * UIScreen.main.scale
let shadowPath: UIBezierPath = UIBezierPath(roundedRect: self.view.bounds, cornerRadius: self.cornerRadius)
//shadowPath.append(UIBezierPath(roundedRect: self.view.bounds.insetBy(dx: 8, dy: 8), cornerRadius: self.cornerRadius))
let shadowWidth: CGFloat = 8.0 // Do this as wide as you want
var outterPath = UIBezierPath()
// Start at the top left corner with an x offset of the cornerRadius
outterPath.move(to: CGPoint(x: self.cornerRadius, y: 0))

// Draw a line to the top right corner
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width - self.cornerRadius, y: 0))
//Draw the round top right corner
outterPath.addArc(withCenter: CGPoint(x: glowView.bounds.size.width - self.cornerRadius, y: self.cornerRadius), radius: self.cornerRadius, startAngle: (3 * CGFloat.pi) / 2, endAngle: 0, clockwise: true)

// Draw a line to the bottom right corner
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width, y: glowView.bounds.size.height - self.cornerRadius))
// Draw the round bottom right corner
outterPath.addArc(withCenter: CGPoint(x: glowView.bounds.size.width - self.cornerRadius, y: glowView.bounds.size.height -  self.cornerRadius), radius: self.cornerRadius, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: true)

// Draw a line to the bottom left corner
outterPath.addLine(to: CGPoint(x: self.cornerRadius, y: glowView.bounds.size.height))
// Draw the round bottom left corner
outterPath.addArc(withCenter: CGPoint(x: self.cornerRadius, y: glowView.bounds.size.height -  self.cornerRadius), radius: self.cornerRadius, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: true)

// Draw a line to the top left corner
outterPath.addLine(to: CGPoint(x: 0.0, y: self.cornerRadius))
// Draw the round top left corner
outterPath.addArc(withCenter: CGPoint(x: self.cornerRadius, y: self.cornerRadius), radius: self.cornerRadius, startAngle: CGFloat.pi, endAngle: (3 * CGFloat.pi) / 2, clockwise: true)

// Move to the inner start point and add the paths counterclockwise to prevent the filling of the inner area
outterPath.move(to: CGPoint(x: shadowWidth + innerRadius, y: shadowWidth))
// Draw the inner top left corner
outterPath.addArc(withCenter: CGPoint(x: shadowWidth + innerRadius, y: shadowWidth + innerRadius), radius: innerRadius, startAngle: (3 * CGFloat.pi) / 2, endAngle: CGFloat.pi, clockwise: false)

// Draw a line to the inner bottom left corner
outterPath.addLine(to: CGPoint(x: shadowWidth, y: glowView.bounds.size.height - innerRadius - shadowWidth))
// Draw the inner bottom left corner
outterPath.addArc(withCenter: CGPoint(x: shadowWidth + innerRadius, y: glowView.bounds.size.height - innerRadius - shadowWidth), radius: innerRadius, startAngle: CGFloat.pi, endAngle: CGFloat.pi / 2, clockwise: false)

// Draw a line to the inner bottom right corner
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width - innerRadius - shadowWidth, y: glowView.bounds.size.height - shadowWidth))
// Draw the inner bottom right corner
outterPath.addArc(withCenter: CGPoint(x: glowView.bounds.size.width - innerRadius - shadowWidth, y: glowView.bounds.size.height - innerRadius - shadowWidth), radius: innerRadius, startAngle: CGFloat.pi / 2, endAngle: 0, clockwise: false)

// Draw a line to the inner top right corner
outterPath.addLine(to: CGPoint(x: glowView.bounds.size.width - shadowWidth, y: shadowWidth + innerRadius))
// Draw the inner top right corner
outterPath.addArc(withCenter: CGPoint(x: glowView.bounds.size.width - innerRadius - shadowWidth, y: shadowWidth + innerRadius), radius: innerRadius, startAngle: 0, endAngle: (3 * CGFloat.pi) / 2, clockwise: false)

// Draw a line to the inner top left corner
outterPath.addLine(to: CGPoint(x: shadowWidth + innerRadius, y: shadowWidth))
outterPath.close()
Community
  • 1
  • 1
skaldesh
  • 1,325
  • 3
  • 15
  • 37