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

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

Related

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

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:

UIImage to the Shapelayer in Swift 4

How can we fill a shape layer with image in iOS using swift 4 ? I have tried using below. I got an Black background. Here self is a Shapelayer where i am going to set an Image
let image = UIImage(named: "ACVResources.bundle/temp.png")!
let imageLayer = CALayer()
imageLayer.contents = UIImage(named: "image")?.cgImage // Assign your image
imageLayer.frame = self.frame
imageLayer.mask = self
self.masksToBounds = true
self.setNeedsDisplay()
First, you have to add CALayer(imageLayer) that fill your view after that you need to set that your view.layer mask your shapeLayer.
I give you an example:
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 500))
customView.backgroundColor = .red
view.addSubview(customView)
let imageLayer = CALayer()
let image = UIImage(named: "pxl.jpeg")
imageLayer.contents = image?.cgImage
imageLayer.frame = customView.frame
customView.layer.addSublayer(imageLayer)
let shape = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: customView.frame.size.width / 2, y: 50))
path.addLine(to: CGPoint(x: customView.frame.size.width, y: customView.frame.size.height / 2))
path.addLine(to: CGPoint(x: 0, y: customView.frame.size.height / 2))
path.close()
shape.path = path.cgPath
customView.layer.mask = shape
screenshot of that code

Creating a blurView with a clear cut out

I am creating an overlay of blurredView with a clear rectangle cutout in the middle. My code so far has achieved the opposite, ie a clearView with a blur cutout in the middle instead.
I have adapted my steps from the following posts, I appreciate if anyone could point me in the right direction.
Swift mask of circle layer over UIView
CALayer with transparent hole in it
let blurView = UIVisualEffectView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
blurView.effect = UIBlurEffect(style: UIBlurEffectStyle.dark)
let scanLayer = CAShapeLayer()
scanLayer.path = CGPath(rect: scanRect, transform: nil)
view.addSubview(blurView)
blurView.layer.addSublayer(scanLayer)
blurView.layer.mask = scanLayer
You can do it like this
let scanLayer = CAShapeLayer()
let scanRect = CGRect.init(x: 100, y: 100, width: 200, height: 100)
let outerPath = UIBezierPath(rect: scanRect)
let superlayerPath = UIBezierPath.init(rect: blurView.frame)
outerPath.usesEvenOddFillRule = true
outerPath.append(superlayerPath)
scanLayer.path = outerPath.cgPath
scanLayer.fillRule = kCAFillRuleEvenOdd
scanLayer.fillColor = UIColor.black.cgColor
view.addSubview(blurView)
blurView.layer.mask = scanLayer

How to CGPath or SKShapeNode Rotation

I want to add rotated elliptical paths to one center point. Like image below but random rotated elliptical.
Draw these elliptical orbits with,
roadShape = SKShapeNode(ellipseInRect: centeredRect)
roadShape.strokeColor = UIColor.greenColor()
self.parent!.addChild(roadShape)
need to make small particles follow those elliptical orbits, thats why I need a path.
var followPath = SKAction.followPath(roadShape.path, asOffset: false, orientToPath: true, duration: 10);
particle.runAction(followPath)
I used SKShapeNode to draw and get the path property. But unfortunately SKShapeNode doesn't have any origin or anchorPoint property so I couldn't decent rotate to SKShapeNode. Do you have any suggestion to make it rotate or different way.
Thanks in advance.
let roadShape1 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape1.strokeColor = UIColor.greenColor()
roadShape1.lineWidth = 3
roadShape1.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape1)
let roadShape2 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape2.strokeColor = UIColor.greenColor()
roadShape2.lineWidth = 3
let action2 = SKAction.rotateByAngle(CGFloat(M_PI)*0.75, duration:0)
roadShape2.runAction(action2)
roadShape2.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape2)
let roadShape3 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape3.strokeColor = UIColor.greenColor()
roadShape3.lineWidth = 3
let action3 = SKAction.rotateByAngle(CGFloat(M_PI)*0.25, duration:0)
roadShape3.runAction(action3)
roadShape3.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape3)
You can also use applyTransform to apply the rotation to the bezierPath
instead of the SKShapeNode as follow:
let roadPath1 = UIBezierPath(ovalInRect: CGRect(x: -100, y: -25, width: 200, height: 50))
let roadPath2 = UIBezierPath(ovalInRect: CGRect(x: -100, y: -25, width: 200, height: 50))
roadPath2.applyTransform(CGAffineTransformMakeRotation(45.0 * CGFloat(M_PI) / 180))
let roadPath3 = UIBezierPath(ovalInRect: CGRect(x: -100, y: -25, width: 200, height: 50))
roadPath3.applyTransform(CGAffineTransformMakeRotation(135.0 * CGFloat(M_PI) / 180))
let roadShape1 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape1.path = roadPath1.CGPath
roadPath1.stroke()
roadShape1.lineWidth = 3
roadShape1.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape1)
let roadShape2 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape2.path = roadPath2.CGPath
roadPath2.stroke()
roadShape2.lineWidth = 3
roadShape2.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape2)
let roadShape3 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape3.path = roadPath3.CGPath
roadPath3.stroke()
roadShape3.lineWidth = 3
roadShape3.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape3)

Resources