CAShapeLayer Image Not Appearing - ios

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)
}

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:
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!

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)
}

Related

How to set CAShapeLayer 's strokeColor in swift

I want draw diagonal view.
This is my code.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let path = UIBezierPath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x: 50, y: 50))
let view = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
view.backgroundColor = UIColor.blue
let mask = CAShapeLayer()
mask.frame = view.bounds
mask.path = path.cgPath
mask.strokeColor = UIColor.orange.cgColor
mask.lineWidth = 25
view.layer.mask = mask
self.view.addSubview(view)
}
I set strokeColor to orange. But color is blue backgroundColor
Why does this happen?
Here is the code snippet:
let path = UIBezierPath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x: 50, y: 50))
let view = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
view.backgroundColor = UIColor.green
let diagonal = CAShapeLayer()
diagonal.frame = view.bounds
diagonal.path = path.cgPath
diagonal.strokeColor = UIColor.orange.cgColor
diagonal.lineWidth = 15
diagonal.backgroundColor = UIColor.blue.cgColor
// view.layer.mask = mask
view.layer.addSublayer(diagonal)
self.view.addSubview(view)
This results in:

Add shadow around a masked UIView

I am trying to create a pop-over which will have a tip. Following is the code for this
tipView.frame = CGRect(x: at.x - size.width, y: at.y, width: size.width, height: size.height)
let imgView = UIImageView.init(frame: CGRect(x: 0, y: 0, width: tipView.frame.width, height: tipView.frame.height))
imgView.image = #imageLiteral(resourceName: "popup.png")
tipView.mask = imgView
I masked UIView with an image which is in the shape of pop-over.
Now I want to add shadow to the UIView on all 4 sides. I tried all the methods. But the shadow is not visible
Add following code to add shadow and corner radius to tipView. Clear backgroundColor and make clipsToBounds = false of tipView.
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(roundedRect: tipView.bounds, byRoundingCorners: [.topLeft, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
shapeLayer.fillColor = UIColor.darkGray.cgColor
shapeLayer.masksToBounds = false
shapeLayer.shadowColor = UIColor.darkGray.cgColor
shapeLayer.shadowPath = shapeLayer.path
shapeLayer.shadowOffset = CGSize(width: 0, height: 2)
shapeLayer.shadowOpacity = 0.5
shapeLayer.shadowRadius = 2.0
tipView.layer.insertSublayer(shapeLayer, at: 0)
Add this code to your view layer:
// set the corner radius
layer.cornerRadius = 6.0
layer.masksToBounds = false
// set the shadow properties
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 1.0)
layer.shadowOpacity = 0.2
layer.shadowRadius = 4.0
Please go through the this link for details

How to apply corner radius to certain corners and add shadow

Is there a way to apply rounded corners to certain corners only, and at the same time apply a shadow layer?
I tried doing that using the following extension, but that didn't work for me:
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat, shadow: Bool = false) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
if shadow {
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 2)
self.layer.shadowOpacity = 0.3
self.layer.shadowRadius = 3.0
}
}
}
So, I tossed in some code into playground and had a quick playground, based on the example from How to round only specific corners using maskedCorners
let view = UIView(frame: CGRect(x: 100, y: 100, width: 500, height: 500))
view.backgroundColor = UIColor.white
view.layer.cornerRadius = 15
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0)
view.layer.shadowOpacity = 1.0
view.layer.shadowRadius = 4
//view.layer.shadowPath = UIBezierPath(rect: view.bounds).cgPath
view.layer.shouldRasterize = false
let outter = UIView(frame: CGRect(x: 0, y: 0, width: 700, height: 700))
outter.backgroundColor = UIColor.red
outter.addSubview(view)
outter
I did some testing:
With no corner radius
With masked corner radius
With reduced shawdowRadius
And just to be sure, increased corner radius

How to set the contentGravity variable of CALayer to 'resizeAspectFill'?

I am trying to set the contentGravity variable of a CALayer but I am not quite sure how to do this. I have tried the following without success:
var layer = CAShapeLayer.init()
layer.contentGravity = .resizeAspectFill // This is not recognised
My UIImageView presents the image taken from the camera of the phone. I then use the Vision Framework to detect rectangles, which return the four corner coordinates of the detected rectangle. I then draw the rectangle in the layer of the UIImageView. The content mode of the UIIMageView is 'Aspect Fill'. My rectangle is currently misaligned and i think it has to do with the contentGravity of the layer. Please see image below:
Rectangle drawn into CAShapelayer:
let rec = UIBezierPath.init()
rec.move(to: CGPoint.init(x: topLeft.x, y: topLeft.y))
rec.addLine(to: CGPoint.init(x: topRight.x, y: topRight.y))
rec.addLine(to: CGPoint.init(x: bottomRight.x, y: bottomRight.y))
rec.addLine(to: CGPoint.init(x: bottomLeft.x, y: bottomLeft.y))
rec.close()
self.layer.opacity = 0.4
self.layer.path = rec.cgPath
self.layer.lineWidth = 5
self.layer.strokeColor = UIColor.yellow.cgColor
self.layer.frame = self.imgPreViewOutlet.bounds
self.imgPreViewOutlet.layer.addSublayer(self.layer)
image show misaligned rectangle drawn in the layer of UIImageView
There is no purpose in setting the content gravity of a CAShapeLayer, as it will in no way affect how the shape is drawn. Content gravity is meaningful only for a layer with content, e.g. a CGImage that is drawn into its contents property.
Here's an example where contentsGravity matters; same image, same layer size, different content gravity:
override func viewDidLoad() {
super.viewDidLoad()
do {
let layer = CALayer()
layer.frame = CGRect(x: 100, y: 100, width: 250, height: 200)
layer.contents = UIImage(named:"smiley")!.cgImage
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .center
}
do {
let layer = CALayer()
layer.frame = CGRect(x: 100, y: 300, width: 250, height: 200)
layer.contents = UIImage(named:"smiley")!.cgImage
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .resize
}
}
But if we try exactly the same thing with a shape layer, the content gravity makes no difference:
override func viewDidLoad() {
super.viewDidLoad()
do {
let layer = CAShapeLayer()
layer.frame = CGRect(x: 100, y: 100, width: 250, height: 200)
layer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 20, height: 20), transform: nil)
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .center
}
do {
let layer = CAShapeLayer()
layer.frame = CGRect(x: 100, y: 300, width: 250, height: 200)
layer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 20, height: 20), transform: nil)
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .resize
}
}

Trying to add dashed border separator in tableview

Using this example I am trying to add a dashed border to my UITableView.
Trying to draw dashed border for UITableViewCell
But it does not work. It shows nothing.
func addDashedBottomBorder(to cell: UITableViewCell) {
let color = UIColor.black.cgColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = cell.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: 0)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2.0
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [9,6]
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: shapeRect.height, width: shapeRect.width, height: 0), cornerRadius: 0).cgPath
cell.layer.addSublayer(shapeLayer)
}
I am using this method in cellForRowAt but it shows me nothing and in viewDidLoad, table.separatorStyle = .none.
From the below 2 lines of your code:
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: shapeRect.height, width: shapeRect.width, height: 0), cornerRadius: 0).cgPath
kCALineJoinRound is making upper and lower dash lines but they are overlapping due to the height of UIBezierPath is 0. So, Updated your code as:
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: shapeRect.height, width: shapeRect.width, height: 1), cornerRadius: 0).cgPath
It will hide the lower line and you will get the desired result.
Best Solution:
Rather than hiding lower dash line, you can correct it by providing just lineDashPhase to your CAShapeLayer, as:
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPhase = 3.0 // Add "lineDashPhase" property to CAShapeLayer
shapeLayer.lineDashPattern = [9,6]
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: shapeRect.height, width: shapeRect.width, height: 0), cornerRadius: 0).cgPath
Received line is:

Resources