I would like to animate lineCap property of a CAShapeLayer.
Here is my code:
func animate() {
let animation = CABasicAnimation(keyPath: "lineCap")
animation.toValue = CAShapeLayerLineCap.round
animation.duration = 0.3
//var progressLayer: CAShapeLayer?
progressLayer?.add(animation, forKey: "AnimationKey")
}
Nothing happens. Probably the error is in keyPath, but a can't find proper value
Line cap is not animatable according to the documentation.
https://developer.apple.com/documentation/quartzcore/cashapelayer
Take a look at the documentation:
https://developer.apple.com/documentation/quartzcore/cashapelayer/1521905-linecap
Search for the word "animatable". You won't find it (at least, not with respect to this property). So your expectation that you can animate this property is wrong.
Related
I'm trying to make very basic animation for UIButton. The goal is rotate layer 180 degrees. Here is my animation code which is called from beginTrackingWithTouch:
private func rotateButton() {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.timingFunction = CAMediaTimingFunction(name: "easeIn")
rotationAnimation.toValue = M_PI
rotationAnimation.duration = CollapseButton.kCollapseButtonAnimationDuration
rotationAnimation.repeatCount = 1
rotationAnimation.cumulative = true
layer.addAnimation(rotationAnimation, forKey: "rotationAnimation")
}
Now I'd like to add collapsing view animation when tapping this button. In my VC:
__weak __typeof(self) weakSelf = self;
[UIView animateWithDuration:CollapseButton.kCollapseButtonAnimationDuration animations:^{
CGRect currentFrame = weakSelf.frame;
currentFrame.size.height = 20;
weakSelf.frame = currentFrame;
}];
I have 2 questions:
After button finishes its animation it resets layer position. So, if arrow were showing top, it animated to showing down and finally resets to top. How can I preserve layer orientation?
As you can see animation duration and timing functions are the same. For the reason I cannot understand UIView animates much slower. Any ideas?
Core animation is strange. The animation creates a "presentation layer" that generates the appearance of the change you are animating, but does not actually change the property in question.
In order to get your animation to finish with the object at the end state, you should set both a fromValue (at the starting setting) and a toValue, and then set the property to it's ending value after submitting the animation:
private func rotateButton() {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.timingFunction = CAMediaTimingFunction(name: "easeIn")
rotationAnimation.fromValue = 0.0 //<<--- NEW CODE
rotationAnimation.toValue = M_PI
rotationAnimation.duration = CollapseButton.kCollapseButtonAnimationDuration
rotationAnimation.repeatCount = 1
rotationAnimation.cumulative = true
layer.addAnimation(rotationAnimation, forKey: "rotationAnimation")
layer.transform = CATransformMakeRotation(M_PI) //<<--- NEW CODE
}
You can also set the animation's removeWhenFinished property to true, but that has other complications.
BTW, you should not try to manipulate the frame of a view that has a non-identity transform. Instead, set the scale on the view's transform.
I'm not sure why the 2 animations are taking different amounts of time. I do notice that you are setting the CAAnimation's timing function to easeIn, but leaving the UIView's timing function as the default (ease-in, ease-out.) That will create animations that don't look the same. You should probably set your view animation to use easeIn timing as well. (To do that you'll need to use the longer form animateWithDuration:delay:options:animations:completion:)
This is the sample code:
func textFieldDidBeginEditing(textField: UITextField) {
self.view.bringSubviewToFront(textField)
let anime = CABasicAnimation(keyPath: "shadowOffset")
anime.toValue = NSValue(CGSize: CGSizeMake(0, 10))
anime.duration = 0.3
textField.layer.addAnimation(anime, forKey: "to")
textField.layer.shadowOffset = CGSizeMake(0, 10)
}
I think this code works fine before. The switch between PresentationLayer and modalLayer is perfect.
However, here shadowOffset is set to CGSizeMake(0, 10) immediately.
When i give the animation a fromValue
anime.fromValue = NSValue(CGSize: CGSizeMake(0, 0))
The animation works.
The document mentioned the former situation:
toValue is non-nil. Interpolates between the current value of keyPath in the target layer’s presentation layer and toValue.
So why i cannot get the animation accurate without setting the fromValue now?
ps: i am using XCode6.3
You need a fromValue because the animation doesn't know what it should animate from. Setting the toValue and animating jumps the content because without a start value, it doesn't know how to interpolate the values.
You can set the fromValue to the current value, like anime.fromValue = textField.layer.shadowOffset.
This is the code I’m using to animate my CAShapeLayer:
_progressBarLayer.strokeEnd = CGFloat(_progressToDrawForProgress(progress))
let progressAnimation = CABasicAnimation(keyPath: "strokeEnd")
progressAnimation.duration = CFTimeInterval(1.0)
progressAnimation.fromValue = CGFloat(self.progress)
progressAnimation.toValue = _progressBarLayer.strokeEnd
progressAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
_progressBarLayer.addAnimation(progressAnimation, forKey: "progressAnimation")
I’ve tested using the delegate to see if the animation plays, and it does. Logging start and stop in the right place.
This code is in a setProgress(progress: CGFloat, animated: Bool) function and runs if animated is true.
Is there anything glaringly obvious here?
Long answer: It turns out that the animation wasn’t playing because of something being drawn above the CAShapeLayer using quartz, so what I thought was the CAShapeLayer (that should be animated) was actually a Quartz drawing of that same layer.
Short answer: Don’t draw to the graphics context with Quartz
Using the code below I've been trying to get my CALayer to scale around its center - but it scales by left/top (0,0). I saw this question: Anchor Point in CALayer and have tried setting the anchorPoint - but it was [.5, .5] before I set it and setting makes no difference. Also tried setting contentsGravity.
The set up is I have a CAShapeLayer added to self.view.layer I do some drawing in it and then add the CABasicAnimation. The animation runs fine - but it scales toward the top/left - not the center of the view.
Have been tinkering with this for a few hours - it must be something simple but I'm not getting it.
shapeLayer.anchorPoint = CGPointMake(.5,.5);
shapeLayer.contentsGravity = #"center";
printf("anchorPoint %f %f\n", shapeLayer.anchorPoint.x, shapeLayer.anchorPoint.y);
CABasicAnimation *animation =
[CABasicAnimation animationWithKeyPath:#"transform.scale"];
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)];
[animation setDuration:1.0];
animation.fillMode=kCAFillModeForwards;
animation.removedOnCompletion=NO;
[shapeLayer addAnimation:animation forKey:#"zoom"];
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.
Also make sure that you're adding the animation after the view is created. If you're trying to add animation in the viewDidLoad, it probably won't work. Try adding it to viewDidAppear instead.
I only know Swift, but here's an example:
override func viewDidAppear (animated: Bool) {
addPulsation(yourButton)
}
func addPulsation (button: UIButton) {
let scaleAnimation:CABasicAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 1.0
scaleAnimation.repeatCount = 100
scaleAnimation.autoreverses = true
scaleAnimation.fromValue = 1.05;
scaleAnimation.toValue = 0.95;
button.layer.addAnimation(scaleAnimation, forKey: "scale")
}
It's an old thread but in case this helps anyone.
This ia a coordinate space problem. You just need to set the drawing space and the space of the the path. This code works for me...
...just put this at the start of the animation creation method (inside a method on the parent view)...
// add animation to remap (without affecting other elements)
let animationLayer = CALayer()
layer?.addSublayer(animationLayer). // (don't use optional for iOS)
// set the layer origin to the center of the view (or any center point for the animation)
animationLayer.bounds.origin .x = -self.bounds.size.width / 2.0
animationLayer.bounds.origin .y = -self.bounds.height / 2.0
// make image layer with circle around the zero point
let imageLayer = CAShapeLayer()
animationLayer.addSublayer(imageLayer)
let shapeBounds = CGRect(x: -bounds.size.width/2.0,
y:-bounds.size.height/2.0,
width: bounds.size.width,
height: bounds.size.height)
imageLayer.path = CGPath(ellipseIn: shapeBounds, transform: nil)
imageLayer.fillColor = fillColour
// **** apply your animations to imageLayer here ****
this code works unchanged on iOS and MacOS - apart from the optional needed on layer for MacOS.
(BTW you need to remove the animation layer after the animation finishes!)
I spent hours to find out how to do it according the center but couldn't find any solutions for OSX. I decided to to move layer using CATransform3DMakeTranslation and combine both transforms.
My code:
NSView *viewToTransform = self.view;
[viewToTransform setWantsLayer:YES];
viewToTransform.layer.sublayerTransform = CATransform3DConcat(CATransform3DMakeScale(-1.0f, -1.0f, 1.0f), CATransform3DMakeTranslation(viewToTransform.bounds.size.width, viewToTransform.bounds.size.height, 0));
Before this code, my movie pic alpha is set to 0,
CABasicAnimation* fadein= [CABasicAnimation animationWithKeyPath:#"alpha"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
[[moviepic layer]addAnimation:fadein forKey:#"alpha"];
Nothing happened, if I set alpha to 0.5 beforehand instead, the alpha remains at 0.5 and not animating to 1.
I've seen a code using UIView beginAnimations: around, but I'm teaching core animation so I wondered why CABasicAnimation can't do simple task like this?
[CABasicAnimation animationWithKeyPath:#"opacity"];
UIView exposes this as alpha where as CALayer exposes this as opacity.
For Swift:
let opacity = CABasicAnimation(keyPath: "opacity")
opacity.fromValue = fromValue
opacity.toValue = toValue
opacity.duration = duration
opacity.beginTime = CACurrentMediaTime() + beginTime //If a delay is needed
view.layer.add(opacity, forKey: nil)
If you want to keep the final alpha value, you have to set the current view controller as the delegate of the opacity animation:
opacity.delegate = self
And, in the delegate function animationDidStop, you should do:
extension ViewController: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
view.alpha = toValue
}
}
#ohho answers the posted question. Mine will be a bit more generic. For a list what can and how be animated with CABasicAnimation please refer to Apple's documentation