'CAShapeLayer Image Not Appearing

I have an image.cgImage that I set as the contents to a CAShapeLayer() but it's not appearing. I force unwrap the image so I know that it definitely exists. The round shape appears but the image inside of it doesn't.

let shapeLayer = CAShapeLayer()
var didSubviewsLayout = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if !didSubviewsLayout {
        didSubviewsLayout = true

        setupShapeLayer()
    }
}

func setupShapeLayer() {
    
    shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 200, y: 200, width: 50, height: 50), cornerRadius: 25).cgPath
    
    shapeLayer.strokeColor = nil
    
    shapeLayer.fillColor = UIColor.orange.cgColor
    
    shapeLayer.contents = UIImage(named: "myIcon")!.cgImage // this doesn't crash so the image definitely exists
    
    view.layer.addSublayer(shapeLayer)
}


Solution 1:[1]

The problem is that your shape layer has no size. You have not given it a frame, so it is just an invisible dot at the top right corner. Unfortunately, you can still see the shape, so you're not aware of the fact that you're doing this all wrong. But the content doesn't draw, because the layer is just a dot, so it reveals your code's feet of clay.

So, in this case, change

shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 200, y: 200, width: 50, height: 50), cornerRadius: 25).cgPath

To

shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 50), cornerRadius: 25).cgPath

Incidentally, that is not how to draw a circle correctly, so if that's your goal, stop it. Use ovalIn; that's what it's for. Complete example:

    let shapeLayer = CAShapeLayer()

    shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)
    shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 50, height: 50)).cgPath
    shapeLayer.strokeColor = UIColor.orange.cgColor
    shapeLayer.lineWidth = 4
    shapeLayer.fillColor = nil
    shapeLayer.contents = UIImage(named: "smiley")?.cgImage

    self.view.layer.addSublayer(shapeLayer)

Result:

enter image description here

Note too that you are probably making this mistake with all your shape layers, not just this one, so you will want to go back through all the code in this app (or better, all the code you've ever written!) and write all your shape layers correctly. Layers need frames!

Solution 2:[2]

You could create the rounded path in a separate layer and add it as a mask to the layer that contains the image. Firstly, I would add a frame to the parent layer so that the image can be centred within the layer using contentsGravity.

shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)

shapeLayer.contents = UIImage(named: "myIcon")!.cgImage
        
// Center the image within the layer
shapeLayer.contentsGravity = .center
        
// We can set the orange color here
shapeLayer.backgroundColor = UIColor.orange.cgColor

Then, create the layer that would have the rounded path and set it as a mask to the image layer:

let maskLayer = CAShapeLayer()
        
maskLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 50), cornerRadius: 25).cgPath
        
shapeLayer.mask = maskLayer

Your modified code:

func setupShapeLayer() {
    shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)

    shapeLayer.contents = UIImage(named: "myIcon")!.cgImage
        
    // Center the image within the layer
    shapeLayer.contentsGravity = .center
        
    // We can set the orange color here
    shapeLayer.backgroundColor = UIColor.orange.cgColor
        
    // Create mask layer
    let maskLayer = CAShapeLayer()
        
    maskLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 50), cornerRadius: 25).cgPath
        
    shapeLayer.mask = maskLayer
        
    view.layer.addSublayer(shapeLayer)
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 yjoon