CAKeyframeAnimation counter-clockwise - ios

I want to animate along a bezierPath counter-clockwise.
At the moment the code below only allows me to do it clockwise.
let arrowHead = UIImageView()
arrowHead.image = UIImage(named: "arrowHead.png")
arrowHead.frame = CGRect(x: 55, y: 300, width: 12, height: 12)
let ovalPath = UIBezierPath(ovalInRect: CGRectMake(50, 240, 320, 240))
let ovalShapeLayer = CAShapeLayer()
ovalShapeLayer.fillColor = UIColor.clearColor().CGColor
ovalShapeLayer.strokeColor = UIColor.lightGrayColor().CGColor
ovalShapeLayer.lineWidth = 1.5
ovalShapeLayer.path = ovalPath.CGPath
self.view.layer.addSublayer(ovalShapeLayer)
let myKeyFrameAnimation = CAKeyframeAnimation(keyPath: "position")
myKeyFrameAnimation.path = ovalPath.CGPath
myKeyFrameAnimation.rotationMode = kCAAnimationRotateAuto
myKeyFrameAnimation.repeatCount = Float.infinity
myKeyFrameAnimation.duration = 6.0
// add the animation
arrowHead.layer.addAnimation(myKeyFrameAnimation, forKey: "animate position along path")
self.view.addSubview(arrowHead)
any ideas?

Ok got it.
myKeyFrameAnimation.speed = -1
Was very simple...

At alternative solution is to use bezierPathWithArcCenter... as documented here which allows you to specify the direction of the circle. Using -M_PI_2 as the start angle and 3 * M_PI_2 as the end angle will give a circle beginning and ending at the top.

Related

Swift - CABasicAnimation not working with CALayer

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?

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.

CAKeyframeAnimation rotation don't draw real shadow

I want to rotate an UIImageView with CAKeyframeAnimation. The problem is that the shadow rotates with the object, making a non-real effect... The shadow must remain under the image, not rotating around it
This is my code. Very simple:
let imagen:UIImageView = UIImageView(image:UIImage(named: "material6.png"))
self.view.addSubview(imagen)
imagen.center = CGPoint(x: 100, y: 100)
imagen.layer.shadowOpacity = 0.75
imagen.layer.shadowOffset = CGSize(width: 0, height: 15)
//Animación del ángulo
let animacionAngulo = CAKeyframeAnimation(keyPath: "transform.rotation.z")
var valoresAngulo = [NSNumber]()
let degrees:Float = 360
let radians = degrees * Float.pi / Float(180.0)
valoresAngulo.append(NSNumber(value: 0))
valoresAngulo.append(NSNumber(value: radians))
animacionAngulo.values = valoresAngulo
//Permite añadir varias animaciones y gestionarlas a la vez
animacionAngulo.repeatCount = Float.infinity
animacionAngulo.duration = 2.0
imagen.layer.add(animacionAngulo, forKey: nil)
Any solution?
If the shadow is rotating along with the image. You can solve it by adding an UIView with same dimensions of your UIImageView below your UIImageView. If you add shadow to your UIView instead of UIImageView, you can achieve the desired effect in this case.
let imagen:UIImageView = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
imagen.center = CGPoint(x: 100, y: 100)
imagen.backgroundColor = UIColor.red
let belowView: UIView = UIView(frame: imagen.frame)
belowView.backgroundColor = UIColor.white
belowView.layer.shadowOpacity = 0.75
belowView.layer.shadowOffset = CGSize(width: 0, height: 15)
self.view.addSubview(belowView)
self.view.addSubview(imagen)
//Animación del ángulo
let animacionAngulo = CAKeyframeAnimation(keyPath: "transform.rotation.z")
var valoresAngulo = [NSNumber]()
let degrees:Float = 360
let radians = degrees * Float.pi / Float(180.0)
valoresAngulo.append(NSNumber(value: 0))
valoresAngulo.append(NSNumber(value: radians))
animacionAngulo.values = valoresAngulo
//Permite añadir varias animaciones y gestionarlas a la vez
animacionAngulo.repeatCount = Float.infinity
animacionAngulo.duration = 2.0
imagen.layer.add(animacionAngulo, forKey: nil)

Swift: make image continuously loop in UIBezierPath(overInRect)

I have an image of a blue circle and I want to make it continuously loop in an elliptical path. I created a circle path first, and it was successful, but creating a oval path loops the circle but stops momentarily before it loops again. Is there something I am missing to make the oval path loop without the hesitation. Here is the code for both loops.
func performRotation(){
let path = UIBezierPath(ovalInRect: CGRectMake(50, 50, 220, 100))
let anim = CAKeyframeAnimation(keyPath: "position")
anim.path = path.CGPath
anim.rotationMode = kCAAnimationLinear
anim.repeatCount = Float.infinity
anim.duration = 5.0
//#IBOutlet weak var ball: UIImageView!
ball.layer.addAnimation(anim, forKey: "animate position along path")
//this is the circle path that loops without the stutter
// let ovalStartAngle = CGFloat(90.01 * M_PI/180)
// let ovalEndAngle = CGFloat(90 * M_PI/180)
// let ovalRect = CGRectMake(97.5, 58.5, 125, 125)
//let path = UIBezierPath()
//path.moveToPoint(CGPoint(x: 200, y: 150))
//path.addArcWithCenter(CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)),
//radius: CGRectGetWidth(ovalRect) / 2,
//startAngle: ovalStartAngle,
//endAngle: ovalEndAngle, clockwise: true)
//let anim = CAKeyframeAnimation(keyPath: "position")
//anim.path = path.CGPath
//anim.rotationMode = kCAAnimationLinear
//anim.repeatCount = Float.infinity
//anim.duration = 5.0
//ball.layer.addAnimation(anim, forKey: "animate position along path")
}
Instead of setting the rotationMode, try setting the calculationMode,
anim.calculationMode = kCAAnimationPaced

Substract CALayer from another, blend?

On the following image, the blue circle is only for debug purpose. My goal is that every layer behind the blue circle should be transparent.I only want to keep visible what's outside the blue circle.
Here is the code written in swift :
let croissantView = UIView(frame: CGRectMake(100, 100, 100, 100))
croissantView.backgroundColor = UIColor.clearColor()
self.view.addSubview(croissantView)
let radius = 50 as CGFloat
let width = 50 as CGFloat
let origin = CGPointMake(0, 100)
let end = CGPointMake(100,100)
let highAmplitude = CGPointMake(50,180)
let lowAmplitude = CGPointMake(50,150)
let quad = UIBezierPath()
quad.moveToPoint(origin)
quad.addQuadCurveToPoint(end, controlPoint: highAmplitude)
quad.closePath()
let quad2 = UIBezierPath()
quad2.moveToPoint(origin)
quad2.addQuadCurveToPoint(end, controlPoint: lowAmplitude)
quad2.closePath()
let layer = CAShapeLayer()
layer.path = quad.CGPath
layer.strokeColor = UIColor.greenColor().CGColor
layer.fillColor = UIColor.blackColor().CGColor
croissantView.layer.addSublayer(layer)
let anim = CABasicAnimation(keyPath: "path")
anim.duration = 3
anim.repeatCount = 20
anim.fromValue = quad.CGPath
anim.toValue = quad2.CGPath
anim.autoreverses = true
layer.addAnimation(anim, forKey: "animQuad")
let animRotate = CABasicAnimation(keyPath: "transform.rotation")
animRotate.duration = 5
animRotate.repeatCount = 20
animRotate.fromValue = 0
animRotate.toValue = 360 * CGFloat(M_PI) / 180
croissantView.layer.addAnimation(animRotate, forKey: "animRotate")
let circle = UIBezierPath(arcCenter: croissantView.center, radius: 75, startAngle: 0, endAngle: 360 * CGFloat(M_PI) / 180, clockwise: true);
let circleLayer = CAShapeLayer()
circleLayer.path = circle.CGPath
circleLayer.fillColor = UIColor.blueColor().CGColor
circleLayer.opacity = 0.6
self.view.layer.addSublayer(circleLayer)
Thoughts
Substract from Layer, don't know if feasible
Blend, don't know how to do it with CALayer
Thanks !! :)
There are three things I would suggest.
Change your croissant view to a layer (not sure this was necessary, but this is what I did)
Create a CGPath for your circle shape that can have its mask inverted. You do this by wrapping an outer rect around your circle so that the path looks like this:
Set your fill rule on your circle shape layer to even-odd:
circleLayer.fillRule = kCAFillRuleEvenOdd
This is what it looks like:
I adapted your code to attempt it and this is what I came up with. Your mileage may vary:
func applyMask() {
let mainLayer = CALayer()
mainLayer.bounds = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
mainLayer.position = CGPoint(x: 200.0, y: 200.0)
// Set the color for debugging
mainLayer.backgroundColor = UIColor.orangeColor().CGColor
self.view.layer.addSublayer(mainLayer)
let radius = 50 as CGFloat
let width = 50 as CGFloat
let origin = CGPointMake(0, 100)
let end = CGPointMake(100,100)
let highAmplitude = CGPointMake(50,180)
let lowAmplitude = CGPointMake(50,150)
let quad = UIBezierPath()
quad.moveToPoint(origin)
quad.addQuadCurveToPoint(end, controlPoint: highAmplitude)
quad.closePath()
let quad2 = UIBezierPath()
quad2.moveToPoint(origin)
quad2.addQuadCurveToPoint(end, controlPoint: lowAmplitude)
quad2.closePath()
let layer = CAShapeLayer()
layer.path = quad.CGPath
layer.strokeColor = UIColor.greenColor().CGColor
layer.fillColor = UIColor.blackColor().CGColor
mainLayer.addSublayer(layer)
let anim = CABasicAnimation(keyPath: "path")
anim.duration = 3
anim.repeatCount = 20
anim.fromValue = quad.CGPath
anim.toValue = quad2.CGPath
anim.autoreverses = true
layer.addAnimation(anim, forKey: "animQuad")
let animRotate = CABasicAnimation(keyPath: "transform.rotation")
animRotate.duration = 5
animRotate.repeatCount = 20
animRotate.fromValue = 0
animRotate.toValue = 360 * CGFloat(M_PI) / 180
mainLayer.addAnimation(animRotate, forKey: "animRotate")
// Build a Circle CGPath
var circlePath = CGPathCreateMutable()
CGPathAddEllipseInRect(circlePath, nil, CGRectInset(mainLayer.bounds, -20.0, -20.0))
// Invert the mask by adding a bounding rectangle
CGPathAddRect(circlePath, nil, CGRectInset(mainLayer.bounds, -100.0, -100.0))
CGPathCloseSubpath(circlePath)
let circleLayer = CAShapeLayer()
circleLayer.fillColor = UIColor.blueColor().CGColor
circleLayer.opacity = 0.6
// Use the even odd fill rule so that our path gets inverted
circleLayer.fillRule = kCAFillRuleEvenOdd
circleLayer.path = circlePath
mainLayer.mask = circleLayer
mainLayer.addSublayer(layer)
}
Here's the project where I tried it out: https://github.com/perlmunger/SwiftVertedMask
Hope that helps.

Resources