3
votes

I'd really appreciate it, if someone could tell me why the code below does not give me the inner shadow, or give me a solution that will give an inner shadow.

I need to create an inner shadow on a rounded UIView. I've been through many answers and found ways of getting this on a normal squared UIViews, but have found no solution that works on a rounded view. Instead I find solutions like the one shown below that look ok to me, but do not create the required inner shadow when I implement them. Here is my screen, it is the white view between the outer blue and inner yellow views that I want to add the shadow to:

outer

I have subclassed the view, here is my draw rect code:

let innerShadow = CALayer()
    // Shadow path (1pt ring around bounds)
    let path = UIBezierPath(rect: innerShadow.bounds.insetBy(dx: -1, dy: -1))
    let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
    path.append(cutout)
    innerShadow.shadowPath = path.cgPath
    innerShadow.masksToBounds = true
    // Shadow properties
    innerShadow.shadowColor = UIColor.darkGray.cgColor
    innerShadow.shadowOffset = CGSize(width: 0.0, height: 7.0)
    innerShadow.shadowOpacity = 1
    innerShadow.shadowRadius = 5
    // Add
    self.layer.addSublayer(innerShadow)

    // Make view round
    self.layer.cornerRadius = self.frame.size.width/2
    self.layer.masksToBounds = true

Many thanks for any help with this. Please do let me know if you have questions.

2

2 Answers

0
votes

Just found this out yesterday

Mask out a circle from the centre of the blue view

    let maskLayer = CAShapeLayer()

    // based on the image the ring is  1 / 6 its diameter
    let radius = self.bounds.width * 1.0 / 6.0
    let path = UIBezierPath(rect: self.bounds)
    let holeCenter = CGPoint(x: center.x - (radius * 2), y: center.y - (radius * 2))

    path.addArc(withCenter: holeCenter, radius: radius, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: true)

    maskLayer.path = path.cgPath
    maskLayer.fillRule = kCAFillRuleEvenOdd

    blueView.layer.mask = maskLayer

The above will give you a blue ring.

Next create a blackView that will act as our shadow

var blackView = UIView()

Set its frame to be the same as the blue view.

 blackView.frame = blueView.frame
 blackView.clipToBounds = true

Cut out a similar hole from the blackView

    let maskLayer = CAShapeLayer()

    // This is the most important part, the mask shadow allows some of the black view to bleed from under the blue view and give a shadow
    maskLayer.shadowOffset = CGSize(width: shadowX, height: shadowY)
    maskLayer.shadowRadius = shadowRadius

    let radius = self.bounds.width * 2.0 / 6.0
    let path = UIBezierPath(rect: self.bounds)
    let holeCenter = CGPoint(x: center.x - (radius * 2), y: center.y - (radius * 2))
    path.addArc(withCenter: holeCenter, radius: radius, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: true)

    maskLayer.path = path.cgPath
    maskLayer.fillRule = kCAFillRuleEvenOdd

    blackView.layer.mask = maskLayer
0
votes

Drop-in subclass of UIView inspired by PaintCode app.

class ShadowView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.groupTableViewBackground

        let lbl = UILabel(frame: .zero)
        addSubview(lbl)
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        lbl.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
        lbl.text = "Text Inside"
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        innerShadowOval(frame: rect)
    }

    func innerShadowOval(frame: CGRect) {
        let context = UIGraphicsGetCurrentContext()!
        context.saveGState()

        // oval color
        let color = UIColor.clear

        // shadow setup
        let shadow = NSShadow()
        shadow.shadowColor = UIColor.black
        shadow.shadowOffset = CGSize(width: 2, height: 0)
        shadow.shadowBlurRadius = 3

        // oval path
        let ovalPath = UIBezierPath(ovalIn: frame)
        color.setFill()
        ovalPath.fill()

        // oval inner shadow
        context.saveGState()
        context.clip(to: ovalPath.bounds)
        context.setShadow(offset: CGSize.zero, blur: 0)
        context.setAlpha((shadow.shadowColor as! UIColor).cgColor.alpha)
        context.beginTransparencyLayer(auxiliaryInfo: nil)
        let ovalOpaqueShadow = (shadow.shadowColor as! UIColor).withAlphaComponent(1)
        context.setShadow(offset: CGSize(width: shadow.shadowOffset.width,
                                         height: shadow.shadowOffset.height),
                          blur: shadow.shadowBlurRadius,
                          color: ovalOpaqueShadow.cgColor)
        context.setBlendMode(.sourceOut)
        context.beginTransparencyLayer(auxiliaryInfo: nil)

        ovalOpaqueShadow.setFill()
        ovalPath.fill()

        context.endTransparencyLayer()
        context.endTransparencyLayer()
        context.restoreGState()
        context.restoreGState()
    }
}

And here goes the result

Result