I'm trying to create pulse animation rectangular shape
I have code:
func createPulse() {
for _ in 0...3 {
let circularPath = UIBezierPath(arcCenter: .zero, radius: view.bounds.size.height/2, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
let pulseLayer = CAShapeLayer()
pulseLayer.path = circularPath.cgPath
pulseLayer.lineWidth = 2
pulseLayer.fillColor = UIColor.clear.cgColor
pulseLayer.lineCap = CAShapeLayerLineCap.round
pulseLayer.position = CGPoint(x: binauralBackView.frame.size.width/2, y: binauralBackView.frame.size.height/2)
pulseLayer.cornerRadius = binauralBackView.frame.width/2
binauralBackView.layer.insertSublayer(pulseLayer, at: 0)
pulseLayers.append(pulseLayer)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animatePulse(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animatePulse(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.animatePulse(index: 2)
}
}
}
}
func animatePulse(index: Int) {
pulseLayers[index].strokeColor = #colorLiteral(red: 0.7215686275, green: 0.5137254902, blue: 0.7647058824, alpha: 1).cgColor
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 2
scaleAnimation.fromValue = 0.0
scaleAnimation.toValue = 0.9
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
scaleAnimation.repeatCount = .greatestFiniteMagnitude
pulseLayers[index].add(scaleAnimation, forKey: "scale")
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
opacityAnimation.duration = 2
opacityAnimation.fromValue = 0.9
opacityAnimation.toValue = 0.0
opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
opacityAnimation.repeatCount = .greatestFiniteMagnitude
pulseLayers[index].add(opacityAnimation, forKey: "opacity")
}
and have result
circle result
I need to create rectangular like this (like border of image):
rectangular
How can change circle to rectangular?
Output:
Note: You can add CABasicAnimation for opacity too in animationGroup if this solution works for you.
extension UIView {
/// animate the border width
func animateBorderWidth(toValue: CGFloat, duration: Double) -> CABasicAnimation {
let widthAnimation = CABasicAnimation(keyPath: "borderWidth")
widthAnimation.fromValue = layer.borderWidth
widthAnimation.toValue = toValue
widthAnimation.duration = duration
return widthAnimation
}
/// animate the scale
func animateScale(toValue: CGFloat, duration: Double) -> CABasicAnimation {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 1.0
scaleAnimation.toValue = toValue
scaleAnimation.duration = duration
scaleAnimation.repeatCount = .infinity
scaleAnimation.isRemovedOnCompletion = false
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
return scaleAnimation
}
func pulsate(animationDuration: CGFloat) {
var animationGroup = CAAnimationGroup()
animationGroup = CAAnimationGroup()
animationGroup.duration = animationDuration
animationGroup.repeatCount = Float.infinity
let newLayer = CALayer()
newLayer.bounds = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
newLayer.cornerRadius = self.frame.width/2
newLayer.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
newLayer.cornerRadius = self.frame.width/2
animationGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
animationGroup.animations = [animateScale(toValue: 6.0, duration: 2.0),
animateBorderWidth(toValue: 1.2, duration: animationDuration)]
newLayer.add(animationGroup, forKey: "pulse")
self.layer.cornerRadius = self.frame.width/2
self.layer.insertSublayer(newLayer, at: 0)
}
}
Usage:
class ViewController: UIViewController {
#IBOutlet weak var pulseView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
pulseView.pulsate(animationDuration: 1.0)
}
}
Related
How can I make this loading indicator using CABasicAnimation?
I was trying to do like this
but it's working not correctly, I am not sure that adding four layers with specific paths and animations is a good idea. I need to make exactly the same I see on the picture. I hope, it's doable using only one layer with some magic
private func setupView() {
let firstLayer = createLayer()
let secondLayer = createLayer()
let thirdLayer = createLayer()
let fourthLayer = createLayer()
firstLayer.path = createBezierPath(
layerWidth: firstLayer.lineWidth,
startAngle: 0,
endAngle: 0.25
).cgPath
secondLayer.path = createBezierPath(
layerWidth: secondLayer.lineWidth,
startAngle: 0.25,
endAngle: 0.5
).cgPath
thirdLayer.path = createBezierPath(
layerWidth: thirdLayer.lineWidth,
startAngle: 0.5,
endAngle: 0.75
).cgPath
fourthLayer.path = createBezierPath(
layerWidth: thirdLayer.lineWidth,
startAngle: 0.75,
endAngle: 1
).cgPath
firstLayer.add(createAnimation(), forKey: "firstLayer")
secondLayer.add(createAnimation(), forKey: "secondLayer")
thirdLayer.add(createAnimation(), forKey: "thirdLayer")
fourthLayer.add(createAnimation(), forKey: "thirdLayer")
self.layer.addSublayer(firstLayer)
self.layer.addSublayer(secondLayer)
self.layer.addSublayer(thirdLayer)
self.layer.addSublayer(fourthLayer)
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.beginTime = 0
animation.duration = 1
animation.fromValue = CGFloat.angle(progress: 0)
animation.toValue = CGFloat.angle(progress: 0.25)
animation.timingFunction = CAMediaTimingFunction(name: .easeIn)
animation.fillMode = .forwards
animation.repeatDuration = .infinity
self.layer.add(animation, forKey: "mainAnim")
}
private func createBezierPath(
layerWidth: CGFloat,
startAngle: CGFloat,
endAngle: CGFloat
) -> UIBezierPath {
return .init(
arcCenter: CGPoint(x: bounds.width / 2, y: bounds.height / 2),
radius: (bounds.height - layerWidth) / 2,
startAngle: .angle(progress: startAngle),
endAngle: .angle(progress: endAngle),
clockwise: true
)
}
private func createAnimation() -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.beginTime = 0
animation.duration = 1
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: .easeIn)
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.repeatDuration = .infinity
return animation
}
private func createLayer() -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.orange.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 5
shapeLayer.lineCap = .round
shapeLayer.strokeEnd = 0
return shapeLayer
}
private extension CGFloat {
static var initialAngle: CGFloat = -(.pi / 2)
static func angle(progress: CGFloat) -> CGFloat {
.pi * 2 * progress + .initialAngle
}
}
I'd use a motion design tool to recreate that. For example, I think you could create this animation with the free version of Flow, which can export Swift code or Lottie files.
I have a scaling/pulsing animation that is working on cgPaths in a for loop as below. This code works but only when you append the animatedLayers to the circleLayer path when the circleLayer has already been added to the subLayer and this creates a static circle (glitch-like) before the animation (DispatchQueue) starts...
...
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
...
Is it possible to add a CAShapeLayer with arguments to a subLayer? If not, any recommended alternative?
import UIKit
import Foundation
#IBDesignable
class AnimatedCircleView: UIView {
// MARK: - Initializers
var animatedLayers = [CAShapeLayer]()
// MARK: - Methods
override func draw(_ rect: CGRect) {
// Animated circle
for _ in 0...3 {
let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
let animatedLayer = CAShapeLayer()
animatedLayer.path = animatedPath.cgPath
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.gray.cgColor
animatedLayer.lineCap = CAShapeLayerLineCap.round
animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
// Dispatch animation for circle _ 0...3
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.animateCircle(index: 3)
}
}
}
}
}
func animateCircle(index: Int) {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 1.8
scaleAnimation.fromValue = 0
scaleAnimation.toValue = 1
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
scaleAnimation.repeatCount = Float.infinity
animatedLayers[index].add(scaleAnimation, forKey: "scale")
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
opacityAnimation.duration = 1.8
opacityAnimation.fromValue = 0.7
opacityAnimation.toValue = 0
opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
opacityAnimation.repeatCount = Float.infinity
animatedLayers[index].add(opacityAnimation, forKey: "opacity")
}
}
The key issue is that your animations start, with staggered delays, between in 0.1 and 1.0 seconds. Until that last animation starts, that layer is just sitting there, full sized and at 100% opacity.
Since you are animating the transform scale from 0 to 1, I’d suggest setting the starting transform to 0 (or changing the opacity to 0). Then you won’t see them sitting there until their respective animations start.
A few other observations:
The draw(_:) is not the right place to add layers, start animations, etc. This method may be called multiple times and should represent the view at a given point in time. I’d retire draw(_:) is it’s not the right place to start this and you don’t need this method at all.
You start your first animation after 0.1 seconds. Why not start it immediately?
You should handle frame adjustments by deferring the setting of the path and position properties until layoutSubviews.
Thus:
#IBDesignable
class AnimatedCircleView: UIView {
private var animatedLayers = [CAShapeLayer]()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(arcCenter: .zero, radius: bounds.width / 2.3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
for animatedLayer in animatedLayers {
animatedLayer.path = path.cgPath
animatedLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
}
}
}
// MARK: - Methods
private extension AnimatedCircleView {
func configure() {
for _ in 0...3 {
let animatedLayer = CAShapeLayer()
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.gray.cgColor
animatedLayer.lineCap = .round
animatedLayer.transform = CATransform3DMakeScale(0, 0, 1)
layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 3)
}
}
}
}
func animateCircle(index: Int) {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 1.8
scaleAnimation.fromValue = 0
scaleAnimation.toValue = 1
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
scaleAnimation.repeatCount = .greatestFiniteMagnitude
animatedLayers[index].add(scaleAnimation, forKey: "scale")
let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
opacityAnimation.duration = 1.8
opacityAnimation.fromValue = 0.7
opacityAnimation.toValue = 0
opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
opacityAnimation.repeatCount = .greatestFiniteMagnitude
animatedLayers[index].add(opacityAnimation, forKey: "opacity")
}
}
Have you tried starting with the fillcolor as clear then turning it to grey at the start of the animation?
// Animated circle
for _ in 0...3 {
let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
let animatedLayer = CAShapeLayer()
animatedLayer.path = animatedPath.cgPath
animatedLayer.strokeColor = UIColor.black.cgColor
animatedLayer.lineWidth = 0
animatedLayer.fillColor = UIColor.clear.cgColor
animatedLayer.lineCap = CAShapeLayerLineCap.round
animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
}
// Dispatch animation for circle _ 0...3
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.animateCircle(index: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animateCircle(index: 1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animateCircle(index: 2)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.animateCircle(index: 3)
}
}
}
}
}
I would like to do animation1 first, then animation2. I would also like to repeat them forever.
let animation1 = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation1.path = circularFirstPath.cgPath
let animation2 = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation2.path = circularSecondPath.cgPath
circleView3.layer.add(animation1, forKey: nil)
circleView3.layer.add(animation2, forKey: nil)
If you want to lead layer along path:
let segmentDuration: CFTimeInterval = 2.0
let rect = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
let path = UIBezierPath(rect: rect)
let posAnimation = CAKeyframeAnimation(keyPath: "position")
posAnimation.duration = segmentDuration
posAnimation.path = path.cgPath
posAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
posAnimation.calculationMode = .paced
posAnimation.repeatCount = .infinity
anyLayer.add(posAnimation, forKey: "position")
If you want to change position try something like this:
let segmentDuration: CFTimeInterval = 2.0
let points = [CGPoint(x: 0, y: 0), CGPoint(x: 400.0, y: 0.0), CGPoint(x: 200.0, y: 200.0)]
let posAnimation = CABasicAnimation(keyPath: "position")
posAnimation.duration = segmentDuration
posAnimation.fromValue = points[0]
posAnimation.toValue = points[1]
let posAnimation1 = CABasicAnimation(keyPath: "position")
posAnimation1.beginTime = posAnimation.duration
posAnimation1.duration = segmentDuration
posAnimation1.fromValue = points[1]
posAnimation1.toValue = points[2]
let posAnimation2 = CABasicAnimation(keyPath: "position")
posAnimation2.beginTime = posAnimation.duration + posAnimation1.duration
posAnimation2.duration = segmentDuration
posAnimation2.fromValue = points[2]
posAnimation2.toValue = points[0]
let positionChain: CAAnimationGroup = CAAnimationGroup()
positionChain.animations = [posAnimation, posAnimation1, posAnimation2]
positionChain.duration = posAnimation.duration + posAnimation1.duration + posAnimation2.duration
positionChain.fillMode = CAMediaTimingFillMode.forwards
positionChain.repeatCount = .infinity
anySubLayer.add(positionChain, forKey: "positionChain")
If you want to change shape of main layer just create CAShapeLayer with shape what you need, mask your layer and start animation:
let segmentDuration: CFTimeInterval = 2.0
let rect = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(rect: rect).cgPath
shapeLayer.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
layer.mask = shapeLayer
let paths = [UIBezierPath(rect: rect), UIBezierPath(roundedRect: rect, cornerRadius: 25), UIBezierPath(ovalIn: rect)]
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.duration = segmentDuration
pathAnimation.fromValue = paths[0].cgPath
pathAnimation.toValue = paths[1].cgPath
let pathAnimation1 = CABasicAnimation(keyPath: "path")
pathAnimation1.beginTime = pathAnimation.duration
pathAnimation1.duration = segmentDuration
pathAnimation1.fromValue = paths[1].cgPath
pathAnimation1.toValue = paths[2].cgPath
let pathAnimation2 = CABasicAnimation(keyPath: "path")
pathAnimation2.beginTime = pathAnimation.duration + pathAnimation1.duration
pathAnimation2.duration = segmentDuration
pathAnimation2.fromValue = paths[2].cgPath
pathAnimation2.toValue = paths[0].cgPath
let pathChain: CAAnimationGroup = CAAnimationGroup()
pathChain.animations = [pathAnimation, pathAnimation1, pathAnimation2]
pathChain.duration = pathAnimation.duration + pathAnimation1.duration + pathAnimation2.duration
pathChain.fillMode = CAMediaTimingFillMode.forwards
pathChain.repeatCount = .infinity
shapeLayer.add(pathChain, forKey: "positionChain")
I am trying to add permanent Shake Animation on UIImageView.
How can i control its Animation timings as well.
var coffeeImageView = UIImageView(image: UIImage(named: "coffee.png"))
shakeImg.frame = CGRectMake(100, self.view.frame.size.height - 100, 50, 50)
self.view.addSubview(coffeeImageView)
let coffeeShakeAnimation = CABasicAnimation(keyPath: "position")
coffeeShakeAnimation.duration = 0.07
coffeeShakeAnimation.repeatCount = 20
coffeeShakeAnimation.autoreverses = true
coffeeShakeAnimation.fromValue = NSValue(cgPoint: CGPointMake(shakeImg.center.x - 10, shakeImg.center.y))
coffeeShakeAnimation.toValue = NSValue(cgPoint: CGPointMake(shakeImg.center.x + 10, shakeImg.center.y))
shakeImg.layer.add(coffeeShakeAnimation, forKey: "position")
You need
var coffeeImageView = UIImageView(image: UIImage(named: "coffee.png"))
coffeeImageView.frame = CGRect(x: 100, y: self.view.frame.size.height - 100, width: 50, height: 50)
self.view.addSubview(coffeeImageView)
let coffeeShakeAnimation = CABasicAnimation(keyPath: "position")
coffeeShakeAnimation.duration = 0.07
coffeeShakeAnimation.repeatCount = 20
coffeeShakeAnimation.autoreverses = true
coffeeShakeAnimation.fromValue = NSValue(cgPoint: CGPoint(x: coffeeImageView.center.x - 10, y: coffeeImageView.center.y))
coffeeShakeAnimation.toValue = NSValue(cgPoint: CGPoint(x: coffeeImageView.center.x + 10, y: coffeeImageView.center.y))
coffeeImageView.layer.add(coffeeShakeAnimation, forKey: "position")
extension UIView {
func shake(_ dur:Double) {
let anim = CABasicAnimation(keyPath: "position")
anim.duration = dur
anim.repeatCount = 20
anim.autoreverses = true
anim.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 10, y: self.center.y))
anim.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 10, y: self.center.y))
self.layer.add(anim, forKey: "position")
}
}
I would prefer using UIView Extension to achieve this animation with reusability.
Swift 4
extension UIView {
func shake(_ duration: Double? = 0.4) {
self.transform = CGAffineTransform(translationX: 20, y: 0)
UIView.animate(withDuration: duration ?? 0.4, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.transform = CGAffineTransform.identity
}, completion: nil)
}
}
Usage
coffeeImageView.shake()
coffeeImageView.shake(0.2)
Try this!
class func shakeAnimation(_ view: UIView) {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.06
animation.repeatCount = 2
animation.autoreverses = true
animation.isRemovedOnCompletion = true
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
animation.fromValue = NSValue(cgPoint: CGPoint(x: view.center.x - 7, y: view.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: view.center.x + 7, y: view.center.y))
view.layer.add(animation, forKey: nil)
}
Is there a method or function that can stop previous animation, when I switch segment controls?
If I don't remove animation, image that was fading on first segment, starts rotating on the second and then moves on the third segment.
While I switch segments, it works more like appending different animations all the time.
I tried:
- removeAllAnimations(), but it doesn't work properly.
- self.view.layer.removeAnimation(forKey: "moveAnimation"). Tried that one at the end of the code for each segment choice.
Maybe I am not putting it in the right spot, but I can't get it working properly
Here is my code:
import UIKit
class MoveViewController: UIViewController {
#IBOutlet var sgAction : UISegmentedControl!
var moveLayer : CALayer?
#IBAction func segmentDidChange(sender : UISegmentedControl){
updateAction()
}
func updateAction(){
let action = sgAction.selectedSegmentIndex
if action == 0 {
//fade
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
self.view.layer.removeAnimation(forKey: "fadeAnimation")
fadeAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
fadeAnimation.fromValue = NSNumber.init(value: 1.0)
fadeAnimation.toValue = NSNumber.init(value: 0.0)
fadeAnimation.isRemovedOnCompletion = false
fadeAnimation.duration = 3.0
fadeAnimation.beginTime = 1.0
fadeAnimation.isAdditive = false
fadeAnimation.fillMode = CAMediaTimingFillMode.both
fadeAnimation.repeatCount = Float.infinity
moveLayer?.add(fadeAnimation, forKey: nil)
} else if action == 1 {
//2. rotate
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
self.view.layer.removeAnimation(forKey: "rotateAnimation")
rotateAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
rotateAnimation.fromValue = 0
rotateAnimation.toValue = 2 * Double.pi
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = 1.0
rotateAnimation.repeatCount = Float.infinity
moveLayer?.add(rotateAnimation, forKey:nil)
} else {
//3. move
let moveAnimation = CABasicAnimation(keyPath: "position")
self.view.layer.removeAnimation(forKey: "moveAnimation")
moveAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
moveAnimation.fromValue = NSValue.init(cgPoint: CGPoint(x: 0, y: 0))
moveAnimation.toValue = NSValue.init(cgPoint: CGPoint(x: 700, y: 500))
moveAnimation.isRemovedOnCompletion = false
moveAnimation.duration = 3.0
moveAnimation.repeatCount = Float.infinity
moveLayer?.add(moveAnimation, forKey: nil)
}
}
// moveLayer?.removeAllAnimations()
func createImg(){
let displayImage = UIImage(named: "balloon.png")
moveLayer = CALayer.init()
moveLayer?.contents = displayImage?.cgImage
moveLayer?.bounds = CGRect(x: 0, y: 0, width: 150, height: 150)
moveLayer?.position = CGPoint(x: 300, y: 200)
self.view.layer.addSublayer(moveLayer!)
}
override func viewDidLoad() {
super.viewDidLoad()
createImg()
// moveLayer?.removeAllAnimations()
updateAction()
}
}
When you add moveLayer?.removeAllAnimations() and self.moveLayer.layoutIfNeed() to the segmentDidChange(sender : UISegmentedControl). Also the other key item that needs to get changed is moveLayer?.add(fadeAnimation, forKey: "nil") to moveLayer?.add(fadeAnimation, forKey: "fadeAnimation").
import UIKit
class MoveViewController: UIViewController {
#IBOutlet var sgAction : UISegmentedControl!
var moveLayer : CALayer?
override func viewDidLoad() {
super.viewDidLoad()
createImg()
updateAction()
}
#IBAction func segmentDidChange(sender : UISegmentedControl){
moveLayer?.removeAllAnimations()
self.moveLayer?.layoutIfNeeded()
updateAction()
}
func updateAction(){
switch sgAction.selectedSegmentIndex {
case 0:
//fade
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
fadeAnimation.fromValue = NSNumber.init(value: 1.0)
fadeAnimation.toValue = NSNumber.init(value: 0.0)
fadeAnimation.isRemovedOnCompletion = false
fadeAnimation.duration = 3.0
fadeAnimation.beginTime = 1.0
fadeAnimation.isAdditive = false
fadeAnimation.fillMode = CAMediaTimingFillMode.both
fadeAnimation.repeatCount = Float.infinity
moveLayer?.add(fadeAnimation, forKey: "fadeAnimation")
case 1:
//2. rotate
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
rotateAnimation.fromValue = 0
rotateAnimation.toValue = 2 * Double.pi
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = 1.0
rotateAnimation.repeatCount = Float.infinity
moveLayer?.add(rotateAnimation, forKey: "rotateAnimation")
case 2:
//3. move
let moveAnimation = CABasicAnimation(keyPath: "position")
moveAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
moveAnimation.fromValue = NSValue.init(cgPoint: CGPoint(x: 0, y: 0))
moveAnimation.toValue = NSValue.init(cgPoint: CGPoint(x: 700, y: 500))
moveAnimation.isRemovedOnCompletion = false
moveAnimation.duration = 3.0
moveAnimation.repeatCount = Float.infinity
moveLayer?.add(moveAnimation, forKey: "position")
default: break
}
}
func createImg(){
let displayImage = UIImage(named: "balloon.png")
moveLayer = CALayer.init()
moveLayer?.contents = displayImage?.cgImage
moveLayer?.bounds = CGRect(x: 0, y: 0, width: 150, height: 150)
moveLayer?.position = CGPoint(x: 300, y: 200)
self.view.layer.addSublayer(moveLayer!)
}
}