Swift - CABasicAnimation not working with CALayer - ios

Here is my code about add a layer with animation added.
let myLayer: CALayer = .init()
let myAnimation: CABasicAnimation = .init(keyPath: "bounds.size.height")
// for myLayer to center of superview
let centerPoint: CGPoint = .init(x: mySuperview.bounds.width / 2, y: mySuperview.bounds.height / 2)
myLayer.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
myLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
myLayer.position = centerPoint
myLayer.backgroundColor = UIColor.red.cgColor
myAnimation.fromValue = 20
myAnimation.toValue = 100
myAnimation.duration = 1
myAnimation.beginTime = CACurrentMediaTime() + 1.0
myAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
myLayer.add(myAnimation, forKey: "bounds.size.height")
mySuperview.layer.addSublayer(myLayer)
and myLayer was appeared at correct position. but added animation isn't working.
not only about bounds.size.height. frame.size.height, transform, opacity... I tried all of them but It isn't changing.
I am confused because I have a recollection of working well easily by above code.
Did I skip anything?

Related

ReplicationLayer stopping animation once the first instance finishes animating, but the other instance animations are cut short

I have attached a gif of what the animation looks like when setup as described below. How can I ensure that all 6 instances of the replicatorLayer finish animating and then remove the leftover coin image (i feel using dispatchQueue.asyncAfter is insufficient for animationPurposes, but am unaware of another solution).
I have a CAReplicationLayer as the first and only subLayer of my views primary CALayer object.
let replicatorLayer = CAReplicatorLayer()
replicatorLayer.instanceDelay = TimeInterval(0.1)
replicatorLayer.instanceCount = 6
replicatorLayer.frame.size = frame.size
layer.addSublayer(replicatorLayer)
I create another CALayer for an image and some text and add it as a sublayer replicatorLayer
let coinWithTextLayer = CALayer()
coinWithTextLayer.frame = CGRect(x: circleCenter.x, y: circleCenter.y, width: 61, height: 67)
coinWithTextLayer.masksToBounds = false
let imageLayer = CALayer()
imageLayer.contents = image.cgImage
imageLayer.frame = CGRect(x: 0, y: 20, width: 49, height: 49)
imageLayer.contentsGravity = .resizeAspect
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: 34, y: 0, width: 40, height: 19)
textLayer.fontSize = 20
textLayer.string = "+ \(5)"
coinWithTextLayer.addSublayer(imageLayer)
coinWithTextLayer.addSublayer(textLayer)
replicatorLayer.addSublayer(coinWithTextLayer)
I then apply an animation group to the coinWithTextLayer that animates it up and to the right, while also scaling from 0.1 to 1
let positionAnim1 = CABasicAnimation(keyPath: "position")
positionAnim1.fromValue = circleCenter
positionAnim1.toValue = CGPoint(x: circleCenter.x + 24, y: circleCenter.y - 24)
let scaleAnim1 = CABasicAnimation(keyPath: "transform.scale")
scaleAnim1.fromValue = 0.1
scaleAnim1.toValue = 1
scaleAnim1.delegate = self
let firstAnimationGroup = CAAnimationGroup()
firstAnimationGroup.beginTime = 0
firstAnimationGroup.duration = 2
firstAnimationGroup.autoreverses = false
firstAnimationGroup.animations = [positionAnim1, scaleAnim1]
firstAnimationGroup.delegate = self
firstAnimationGroup.fillMode = .backwards
coinWithTextLayer.add(firstAnimationGroup, forKey: "tapCoinAnim1")
/*DispatchQueue.main.asyncAfter(seconds: 0.5 + (0.1 * 6)) { // 0.1 * 6 to account for the delay between instances of the replicatorLayer
coinWithTextLayer.removeFromSuperlayer()
coinWithTextLayer.removeAllAnimations()
}*/ // Commented out because it does successfuly remove the coin after the animation, but feels like an incorrect/inefficient way of doing so

What is the proper way to scale a view while animating it with CAKeyframeAnimation and BezierPath

The following code effectively animates and scales a view along a BezierPath but I feel like I'm cheating since I'm mixing UIKit and CAKeyframeAnimation (Core Animation) animations.
Is there a better way to scale a view while animating it along the BezierPath?
func animateIt(){
let myView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
myView.backgroundColor = .blue
myView.layer.cornerRadius = myView.frame.height / 2
view.addSubview(myView)
let startPoint = CGPoint(x: 250, y: 500)
let apexPoint = CGPoint(x: 100, y: 50)
let endPoint = CGPoint(x: 50, y: 350)
let durationTime = 1.5
let path = UIBezierPath()
path.move(to: startPoint)
path.addQuadCurve(to: endPoint, controlPoint: apexPoint)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.duration = durationTime
myView.layer.add(animation, forKey: "bezier")
myView.center = endPoint
UIView.animate(withDuration: durationTime, animations: {
myView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5);
})
}
I tried using a second CAKeyframeAnimation animation instead of the UIKit animation and used the array of key values but no matter what I do, I cannot get a uniform scale, it scales at the beginning but not at the end.
let scaleAnimation = CAKeyframeAnimation(keyPath: "scale")
scaleAnimation.values = [1.0, 0.9, 0.5,]
myView.layer.add(scaleAnimation, forKey: "scale")
Thanks!

CABasicAnimation not scaling around center

I want to perform opacity And Scale effect at same time my animation work perfect but it's position is not proper. i want to perform animation on center.
This is my code.
btn.backgroundColor = UIColor.yellowColor()
let stroke = UIColor(red:236.0/255, green:0.0/255, blue:140.0/255, alpha:0.8)
let pathFrame = CGRectMake(24, 13, btn.bounds.size.height/2, btn.bounds.size.height/2)
let circleShape1 = CAShapeLayer()
circleShape1.path = UIBezierPath(roundedRect: pathFrame, cornerRadius: btn.bounds.size.height/2).CGPath
circleShape1.position = CGPoint(x: 2, y: 2)
circleShape1.fillColor = stroke.CGColor
circleShape1.opacity = 0
btn.layer.addSublayer(circleShape1)
circleShape1.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
scaleAnimation.toValue = NSValue(CATransform3D: CATransform3DMakeScale(2.0, 2.0, 1))
let alphaAnimation = CABasicAnimation(keyPath: "opacity")
alphaAnimation.fromValue = 1
alphaAnimation.toValue = 0
CATransaction.begin()
let animation = CAAnimationGroup()
animation.animations = [scaleAnimation, alphaAnimation]
animation.duration = 1.5
animation.repeatCount = .infinity
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
circleShape1.addAnimation(animation, forKey:"Ripple")
CATransaction.commit()
The problem is that you are not grappling with frames correctly. You say:
let circleShape1 = CAShapeLayer()
But you have forgotten to give circleShape1 a frame! Thus, its size is zero, and very weird things happen when you animate it. Your job in the very next line should be to assign circleShape1 a frame. Example:
circleShape1.frame = pathFrame
That may or may not be the correct frame; it probably isn't. But you need to figure that out.
Then, you need to fix the frame of your Bezier path in terms of the shape layer's bounds:
circleShape1.path = UIBezierPath(roundedRect: circleShape1.bounds // ...
I have never worked with sublayers so I would make it with a subview instead, makes the code a lot easier:
btn.backgroundColor = UIColor.yellow
let circleShape1 = UIView()
circleShape1.frame.size = CGSize(width: btn.frame.height / 2, height: btn.frame.height / 2)
circleShape1.center = CGPoint(x: btn.frame.width / 2, y: btn.frame.height / 2)
circleShape1.layer.cornerRadius = btn.frame.height / 4
circleShape1.backgroundColor = UIColor(red:236.0/255, green:0.0/255, blue:140.0/255, alpha:0.8)
circleShape1.alpha = 1
btn.addSubview(circleShape1)
UIView.animate(withDuration: 1,
delay: 0,
options: [.repeat, .curveLinear],
animations: {
circleShape1.transform = CGAffineTransform(scaleX: 5, y: 5)
circleShape1.alpha = 0.4
}, completion: nil)
You need to create path frame with {0,0} position, and set correct frame to the Layer:
let pathFrame = CGRectMake(0, 0, btn.bounds.size.height/2, btn.bounds.size.height/2)
....
circleShape1.frame = CGRect(x: 0, y: 0, width: pathFrame.width, height: pathFrame.height)
circleShape1.position = CGPoint(x: 2, y: 2)
If you want to create path with {13,24} position you need to change width and height in layer. Your shape should be in center of layer.

Animation with Swift

I want to hav this kind of animation in my app, a case where an arrow shape such that when clicked it transforms into a mark.
This is what i have at present using the code below
func handleTransform(){
let arrowPath = UIBezierPath()
arrowPath.move(to: CGPoint(x: 50, y: 0))
arrowPath.addLine(to: CGPoint(x: 25, y: 75))
arrowPath.addLine(to: CGPoint(x: 25, y: 75))
arrowPath.addLine(to: CGPoint(x: 25, y: 45))
arrowPath.addLine(to: CGPoint(x: 25, y: 35))
arrowPath.close()
let progressLines = CAShapeLayer()
progressLines.path = arrowPath.cgPath
progressLines.strokeColor = UIColor.black.cgColor
progressLines.fillColor = UIColor.clear.cgColor
progressLines.lineWidth = 10.0
progressLines.lineCap = kCALineCapRound
self.view.layer.addSublayer(progressLines)
let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
animateStrokeEnd.duration = 3.0
animateStrokeEnd.fromValue = 0.0
animateStrokeEnd.toValue = 1.0
progressLines.add(animateStrokeEnd, forKey: "animate stroke end animation")
}
the flow is illustrated in the images bellow
On click of this image
It transform to this image
I have been able t solve, i did a switch image with animation

CALayer disappears after playing a CABasicAnimation backwards

I have a CALayer and I've added a CABasicAnimation to it like this:
circle = CALayer()
circle.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
circle.backgroundColor = UIColor.green().cgColor
circle.cornerRadius = 50
circle.speed = 0
view.layer.addSublayer(circle)
animation = CABasicAnimation(keyPath: "position")
animation.duration = 1
animation.fromValue = NSValue(cgPoint: CGPoint(x: 50, y: 50))
animation.toValue = NSValue(cgPoint: CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2))
animation.fillMode = kCAFillModeBoth
animation.isRemovedOnCompletion = false
circle.add(animation, forKey: nil)
When I play the animation backwards by setting the layer's speed to -1,
the layer disappears at the end of the animation:
circle.timeOffset = 1
circle.speed = -1
circle.beginTime = CACurrentMediaTime()
Because of that, I use CADisplayLink to play an animation backwards,
but I'm wondering if it can be achieved by setting the speed to -1 and preventing the layer from disappearing also.

Resources