Infinite shake animation for a UIImageView in Swift - ios

I'm trying to get a UIImageView to shake indefinitely.
var coffeeImageView = UIImageView(image: UIImage(named: "coffee.png"))
coffeeImageView.frame = CGRectMake(100, self.view.frame.size.height - 100, 50, 50)
self.view.addSubview(coffeeImageView)
let coffeeShakeAnimation = CABasicAnimation(keyPath: "transform")
coffeeShakeAnimation.duration = 0.07
coffeeShakeAnimation.repeatCount = 20
coffeeShakeAnimation.autoreverses = true
coffeeShakeAnimation.fromValue = NSValue(CGPoint: CGPointMake(coffeeImageView.center.x - 10, coffeeImageView.center.y))
coffeeShakeAnimation.toValue = NSValue(CGPoint: CGPointMake(coffeeImageView.center.x + 10, coffeeImageView.center.y))
coffeeImageView.layer.addAnimation(coffeeShakeAnimation, forKey: "position")

replace
let coffeeShakeAnimation = CABasicAnimation(keyPath: "transform")
by
let coffeeShakeAnimation = CABasicAnimation(keyPath: "position")
it will shake

Related

image disappears after keyframe animation

I have an animation where a line is drawn and then with that another image moves. The animation works good but as the animation completes the image disappears from the view. The image should stay after completion of the animation. My code for the animation:
private func addPlaneAnimation() {
let image = UIImageView(frame: CGRect(x: -30, y: view.height*0.8, width: 30, height: 30))
image.image = R.image.plane()
view.addSubview(image)
let path = UIBezierPath()
path.move(to: image.center)
path.addLine(to: CGPoint(x: (view.width*0.85), y: (view.height*0.40)))
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
shapeLayer.strokeColor = R.color.Purple()?.cgColor
shapeLayer.lineWidth = 3
shapeLayer.path = path.cgPath
view.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 4
animation.beginTime = CACurrentMediaTime() + 0.2
animation.isRemovedOnCompletion = false
let pathAnimation = CAKeyframeAnimation(keyPath: "position")
pathAnimation.path = path.cgPath
pathAnimation.duration = 4
pathAnimation.autoreverses = false
pathAnimation.isRemovedOnCompletion = false
shapeLayer.add(animation, forKey: "MyAnimation")
image.layer.add(pathAnimation, forKey: "position")
view.bringSubviewToFront(cardBarcode)
}
Can someone please help me here?
Just add two more properties.
For strokeEnd animation
animation.fillMode = CAMediaTimingFillMode.backwards
and for pathAnimation
pathAnimation.fillMode = CAMediaTimingFillMode.forwards

CALayer path animation not working

Why does this path animation not work?
let frame = CGRect(x: 20, y: 20, width: 10, height: 10)
let frame2 = CGRect(x: 20, y: 20, width: 100, height: 100)
let path = UIBezierPath(rect: frame)
let path2 = UIBezierPath(rect: frame2)
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = path
animation.toValue = path2
animation.duration = 3
animation.repeatCount = Float.greatestFiniteMagnitude
let l = CAShapeLayer()
l.path = path.cgPath
l.fillColor = UIColor.black.cgColor
self.view.layer.addSublayer(l)
l.add(animation, forKey: "pathAnimation")
Complete playground:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
let frame = CGRect(x: 20, y: 20, width: 10, height: 10)
let frame2 = CGRect(x: 20, y: 20, width: 100, height: 100)
let path = UIBezierPath(rect: frame)
let path2 = UIBezierPath(rect: frame2)
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = path
animation.toValue = path2
animation.duration = 3
animation.repeatCount = Float.greatestFiniteMagnitude
let l = CAShapeLayer()
l.path = path.cgPath
l.fillColor = UIColor.black.cgColor
self.view.layer.addSublayer(l)
l.add(animation, forKey: "pathAnimation")
}
}
PlaygroundPage.current.liveView = MyViewController()
You should use cgPath in animation
animation.fromValue = path.cgPath
animation.toValue = path2.cgPath

how to control strokeEnd animation Line Speed

I want to create an atomic motion animation in iOS, but there are some trouble could not solve.
here is my animation result
CAKeyframeAnimation position animation along bezierPath does not match the CABasicAnimation strokeEnd animation(particles animation does not match their wake animation).
2.The wake animation loop is incoherent, CABasicAnimation seems can't represent the Curving segment that strokeStart is positive and strokeEnd is negative.
my code:
let atom = CAShapeLayer()
var rect = CGRect(x: 0,y: 0,width: 5,height: 5)
var path = UIBezierPath(roundedRect: rect,cornerRadius: 5.0)
atom.path = path.CGPath;
atom.strokeColor = UIColor.greenColor().CGColor
atom.bounds = rect;
rect = CGRect(x: 0,y: 0,width: 300,height: 50);
path = UIBezierPath(ovalInRect:rect);
let orbit = CAShapeLayer()
orbit.path = path.CGPath;
orbit.strokeColor = UIColor.whiteColor().CGColor
orbit.lineWidth = 1;
orbit.fillColor = UIColor.clearColor().CGColor
orbit.bounds = CGPathGetPathBoundingBox(path.CGPath);
orbit.position = CGPoint(x:150,y:25)
rect = CGRect(x: 0,y: 0,width: 300,height: 50);
path = UIBezierPath(ovalInRect:rect);
let keyani = CAKeyframeAnimation(keyPath: "position");
keyani.path = path.CGPath
keyani.duration = 3.0;
keyani.repeatCount = HUGE;
keyani.additive = true
keyani.calculationMode = kCAAnimationPaced
keyani.rotationMode = kCAAnimationRotateAuto
keyani.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionLinear);
atom.addAnimation(keyani, forKey: "")
let orbitAni = CABasicAnimation(keyPath: "strokeStart")
orbitAni.byValue = 1.0
orbitAni.cumulative = true
let orbitAni1 = CABasicAnimation(keyPath: "strokeEnd")
orbitAni1.fromValue = 0.0
orbitAni1.toValue = 1.0
let animationGroup = CAAnimationGroup()
animationGroup.animations = [orbitAni,orbitAni1]
animationGroup.duration = 3.0
animationGroup.repeatCount = HUGE
orbit.strokeStart = -0.2
orbit.addAnimation(animationGroup, forKey: "");
orbit.shadowRadius = 5.0;
orbit.shadowColor = UIColor.greenColor().CGColor
orbit.shadowOpacity = 0.5
orbit.shadowOffset = CGSizeZero
var trans = CGAffineTransformIdentity
trans = CGAffineTransformRotate(trans, CGFloat(M_PI_4))
repeator.instanceTransform = CATransform3DMakeAffineTransform (trans)
repeator.instanceCount = 4;
repeator.instanceDelay = 0;
repeator.addSublayer(orbit)
repeator.addSublayer(atom)
repeator.bounds = CGRect(x: 0,y: 0,width: 300,height: 50)
repeator.position = CGPoint(x:200,y:200)
repeator.anchorPoint = CGPoint(x:0.5,y:0.5)
repeator.instanceAlphaOffset = 1.0

CABasicAnimation runs after Implicit Animation

I want a layer to behave like this:
Instead, it behaves like this:
The card flip animation is created by two CABasicAnimations applied in a CAAnimationGroup. The incorrect spin effect happens because the implicit animation from the CALayer property change runs first and then my animation specified in the CABasicAnimation runs. How can I stop the implicit animation from running so that only my specified animation runs?
Here's the relevant code:
class ViewController: UIViewController {
var simpleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tap)
simpleLayer.frame = CGRect(origin: CGPoint(x: view.bounds.width / 2 - 50, y: view.bounds.height / 2 - 50), size: CGSize(width: 100, height: 100))
simpleLayer.backgroundColor = UIColor.blackColor().CGColor
view.layer.addSublayer(simpleLayer)
}
func handleTap() {
let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
xRotation.toValue = 0
xRotation.byValue = M_PI
let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
yRotation.toValue = 0
yRotation.byValue = M_PI
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
let group = CAAnimationGroup()
group.animations = [xRotation, yRotation]
group.duration = 0.6
group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
simpleLayer.addAnimation(group, forKey: nil)
}
}
#LucasTizma had the correct answer.
Surround your animation with CATransaction.begin(); CATransaction.setDisableActions(true) and CATransaction.commit(). This will disable the implicit animation and make the CAAnimationGroup animate correctly.
Here's the final result:
This is the important snippet of code in Swift 3:
CATransaction.begin()
CATransaction.setDisableActions(true)
let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
xRotation.toValue = 0
xRotation.byValue = M_PI
let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
yRotation.toValue = 0
yRotation.byValue = M_PI
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")
let group = CAAnimationGroup()
group.animations = [xRotation, yRotation]
simpleLayer.add(group, forKey: nil)
CATransaction.commit()
And this is the full code for the depicted animation with an iOS app:
class ViewController: UIViewController {
var simpleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tap)
let ratio: CGFloat = 1 / 5
let viewWidth = view.bounds.width
let viewHeight = view.bounds.height
let layerWidth = viewWidth * ratio
let layerHeight = viewHeight * ratio
let rect = CGRect(origin: CGPoint(x: viewWidth / 2 - layerWidth / 2,
y: viewHeight / 2 - layerHeight / 2),
size: CGSize(width: layerWidth, height: layerHeight))
let topRightPoint = CGPoint(x: rect.width, y: 0)
let bottomRightPoint = CGPoint(x: rect.width, y: rect.height)
let topLeftPoint = CGPoint(x: 0, y: 0)
let linePath = UIBezierPath()
linePath.move(to: topLeftPoint)
linePath.addLine(to: topRightPoint)
linePath.addLine(to: bottomRightPoint)
linePath.addLine(to: topLeftPoint)
let maskLayer = CAShapeLayer()
maskLayer.path = linePath.cgPath
simpleLayer.frame = rect
simpleLayer.backgroundColor = UIColor.black.cgColor
simpleLayer.mask = maskLayer
// Smooth antialiasing
// * Convert the layer to a simple bitmap that's stored in memory
// * Saves CPU cycles during complex animations
// * Rasterization is set to happen during the animation and is disabled afterwards
simpleLayer.rasterizationScale = UIScreen.main.scale
view.layer.addSublayer(simpleLayer)
}
func handleTap() {
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.setCompletionBlock({
self.simpleLayer.shouldRasterize = false
})
simpleLayer.shouldRasterize = true
let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
xRotation.toValue = 0
xRotation.byValue = M_PI
let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
yRotation.toValue = 0
yRotation.byValue = M_PI
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")
let group = CAAnimationGroup()
group.animations = [xRotation, yRotation]
group.duration = 1.2
group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
simpleLayer.add(group, forKey: nil)
CATransaction.commit()
}
}
You are creating 2 separate animations and applying them in an animation group. When you do that they are applied as 2 discrete steps.
It looks like that's not what you want. If not, then don't create 2 separate animations, one on transform.rotation.x and the other on transform.rotation.y. Instead, concatenate both changes onto a transformation matrix and apply the changed transformation matrix as a single animation.

Animate Gradient View iOS on Linear path using

I am trying to animate a gradient layer continuously. Please see the below gif?
I am using CAGradientLayer, but it only animates on color at a time.
var gradient: CAGradientLayer!
self.gradient = CAGradientLayer()
self.gradient.frame = CGRect(x: 0, y: 0, width:view.layer.frame.size.width, height: 50)
self.gradient.colors = [UIColor.redColor().CGColor, UIColor.redColor().CGColor]
self.view.layer.addSublayer(self.gradient)
self.animateLayer()
func animateLayer () {
let fromColors: NSArray = self.gradient.colors!
let toColors: NSArray = [UIColor.blueColor().CGColor, UIColor.blueColor().CGColor]
self.gradient.colors = toColors as [AnyObject]
let animation : CABasicAnimation = CABasicAnimation(keyPath: "colors")
animation.fromValue = fromColors
animation.toValue = toColors
animation.duration = 3.00
animation.removedOnCompletion = true
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.delegate = self
self.gradient?.addAnimation(animation, forKey:"animateGradient")
}

Resources