Calayer - Cabasicanimation Not Scaling Around Center/Anchorpoint

CALayer - CABasicAnimation not scaling around center/anchorPoint

What is the bounds of your layer?

I bet it has zero size; try setting its size to the same size as your shape.

CAShapeLayer draws the shape as kind of an overlay, independent of the contents (and bounds and contentsGravity and etc.) that you'd use in an ordinary CALayer. It can be a bit confusing.

CABasicAnimation - transform scale keep in center

I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?

CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"position"];

animation2.duration = 1.0f;

animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];

[toBeMask.layer.mask addAnimation:animation2 forKey:@"animateMask2"];

Scaling a CAShapeLayer with CABasicAnimation causes it to translate

"My current theory is that the layer is actually larger than just the circle such that as the entire layer scales..."

Your current theory is correct.

Try it like this:

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

let radius: CGFloat = 20.0

let point = CGPoint(x: radius, y: radius)
let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 2.0

let growthAnimation = CABasicAnimation(keyPath: "transform.scale")
growthAnimation.toValue = 3
growthAnimation.fillMode = .forwards
growthAnimation.isRemovedOnCompletion = false
growthAnimation.duration = 1.0

shapeLayer.add(growthAnimation, forKey: nil)

// set the layer frame
shapeLayer.frame = CGRect(x: view.frame.midX - radius, y: view.frame.midY - radius, width: radius * 2.0, height: radius * 2.0)

shapeLayer.backgroundColor = UIColor.green.cgColor
self.view.layer.addSublayer(shapeLayer)

}

Edit - this may make things more clear...

If you run this example, it creates 4 shape layers with circles:

  • circle center at 0,0 with no layer frame given
  • circle center at 100,100 with no layer frame given
  • circle center at 0,0 with layer frame x: 100, y: 240, w: 40, h: 40 (40 is radius * 2)... the circle center is top-left corner of the frame
  • circle center at radius,radius (so, 20,20) with layer frame x: 100, y: 380, w: 40, h: 40 (40 is radius * 2)... the circle center is now centered in the layer frame

When you tap anywhere on the view, all 4 layers will perform the same scaling animation (slowed to 3-seconds to make it easier to watch).

It should then be clear how the path and layer frame affect the transformation.

class ViewController: UIViewController {

func test1() -> Void {

let radius: CGFloat = 20.0

let shapeLayer = CAShapeLayer()

// no frame set for the shape layer

// point is at top-left corner of shape layer frame
// since we haven't set a frame for the layer, it's top-left corner of the view
let point = CGPoint(x: 0, y: 0)

let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 2.0

self.view.layer.addSublayer(shapeLayer)

shapeLayer.backgroundColor = UIColor.green.cgColor

}

func test2() -> Void {

let radius: CGFloat = 20.0

let shapeLayer = CAShapeLayer()

// no frame set for the shape layer

// set point to 100,100
let point = CGPoint(x: 100, y: 100)

let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 2.0

self.view.layer.addSublayer(shapeLayer)

shapeLayer.backgroundColor = UIColor.green.cgColor

}

func test3() -> Void {

let radius: CGFloat = 20.0

let shapeLayer = CAShapeLayer()

// set shape layer frame to 40x40 at 100,240
shapeLayer.frame = CGRect(x: 100.0, y: 240.0, width: radius * 2.0, height: radius * 2.0)

// point is at top-left corner of shape layer frame
let point = CGPoint(x: 0, y: 0)

let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.orange.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 2.0

self.view.layer.addSublayer(shapeLayer)

shapeLayer.backgroundColor = UIColor.green.cgColor

}

func test4() -> Void {

let radius: CGFloat = 20.0

let shapeLayer = CAShapeLayer()

// set shape layer frame to 40x40 at 100,380
shapeLayer.frame = CGRect(x: 100.0, y: 380.0, width: radius * 2.0, height: radius * 2.0)

// set point to center of layer frame
let point = CGPoint(x: radius, y: radius)

let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2.0, clockwise: true)
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 2.0

self.view.layer.addSublayer(shapeLayer)

shapeLayer.backgroundColor = UIColor.green.cgColor


}

override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setNavigationBarHidden(true, animated: false)
let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap))
view.addGestureRecognizer(t)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

test1()
test2()
test3()
test4()

}

@objc func didTap() -> Void {
scaleLayers()
}

func scaleLayers() -> Void {

let growthAnimation = CABasicAnimation(keyPath: "transform.scale")
growthAnimation.toValue = 3
growthAnimation.fillMode = .forwards
growthAnimation.isRemovedOnCompletion = false
growthAnimation.duration = 3.0

guard let layers = view.layer.sublayers else {
return
}
layers.forEach { layer in
if let shapeLayer = layer as? CAShapeLayer {
shapeLayer.add(growthAnimation, forKey: nil)
}
}

}
}

Why is my image layer rotating around the edge of the image and not the center?

I'm not sure the exact answer to your question as I can't figure out where it's going wrong in the code. By default things should be rotating around the center. I wrote a demo app that shows how to change the rotation for all the axis using a UISlider. I've posted it on github here: https://github.com/perlmunger/AllAxis.git . Here is the gist of the code, though:

- (IBAction)sliderDidChange:(id)sender
{
[self setTransforms];
}

- (void)setTransforms
{
CGFloat x = [_xSlider value];
CGFloat y = [_ySlider value];
CGFloat z = [_zSlider value];

CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f/-800.0f; // perspective

CGFloat rotationValue = x * kRotationMaxInDegrees;

transform = CATransform3DRotate(transform,
degreesToRadians(rotationValue), 1.0f, 0.0f, 0.0f);

rotationValue = y * kRotationMaxInDegrees;

transform = CATransform3DRotate(transform,
degreesToRadians(rotationValue), 0.0f, 1.0f, 0.0f);

rotationValue = z * kRotationMaxInDegrees;

transform = CATransform3DRotate(transform,
degreesToRadians(rotationValue), 0.0f, 0.0f, 1.0f);

[[_rotationView layer] setTransform:transform];

}

This code builds up a collective transform that you apply to the view's layer on each change to any slider.

Core animation anchor point being ignored?

The default anchorPoint is by default 0.5, 0.5, so it should not have to be set.
It is possible that you have positioned the content of the layer so that
it appears as though the origin is really at the anchor point.

scaling an image on a CALayer?

Yes, the layers will scale relative to their anchor point property without changing it.



Related Topics



Leave a reply



Submit