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 framex: 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 framex: 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
Linker Error When Accessing Application Module in UI Tests in Xcode 7.1
Can't Set Background Color of Uitableviewcell in Ib
How to Set the Cornerradius of a Uistackview
How to View Testflight Crash Logs
How to Clear All Cached Images Loaded from Sdwebimage
Header Displaced in Tableview with Uirefreshcontrol
While Mail Sending Process Using iOS 8 Simulators Generates Error
Fcm Notification in iOS Doesn't Play Sound When Received
Swiftui in iOS14 Keyboard Avoidance Issues and Ignoressafearea Modifier Issues
Dynamic Uicollectionview Header Size Based on Uilabel
iOS Enterprise Provisioning Profile Expiration
Uidatepicker Bug? Uicontroleventvaluechanged After Hitting Minimum Internal
How to Get Audio Controls on Lock Screen/Control Center from Avaudioplayer in Swift
Add Uiview Banner Above Status Bar iOS 7
How to Get Previous and Next Month in Calendar-Swift
How to Pop View Controller to One of the Previous View Controllers in Swift