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)
}
Related
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)
}
}
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 want an animating imageView with UIBezierPath (sliding from top to bottom) to be clicked. But every google help I found didn't solve it. But it does not print "clicked".
The animation does work perfectly. Maybe anyone's got a clue?
Help is very appreciated.
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "advent_button"))
let dimension = 25 + drand48() * 30
imageView.frame = CGRect(x: 0, y: 0, width: dimension, height: dimension)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleImageTap))
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.isUserInteractionEnabled = true
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = customPath().cgPath
animation.duration = 20
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.repeatCount = 0.5
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
imageView.layer.add(animation, forKey: nil)
view.addSubview(imageView)
}
#objc func handleImageTap() {
print ("clicked")
}
func customPath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 200, y: 0))
let endpoint = CGPoint(x: 20 , y: 700)
let cp1 = CGPoint(x: 90, y: 250)
let cp2 = CGPoint(x: 180, y: 400)
path.addCurve(to: endpoint, controlPoint1: cp1, controlPoint2: cp2)
return path
}
I've tried the following:
UIView.animate(withDuration: 30, delay: delay, options: [.allowUserInteraction], animations: {
self.button.frame = CGRect(
x: randomXEndShift,
y: 700,
width: dimension,
height: dimension)
}, completion: nil)
Shows the same behavoir. Even using .allowUserInteraction does not help at all.
I got your issue. i am sure you are touching layer not UIImageView and you have added uitapgesturerecognizer to UIImageview not layer. Do one thing set
animation.isRemovedOnCompletion = true
and set imageView.frame similar co- ordinates where animation will stop
Full code :
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "Icon123"))
imageView.backgroundColor = .red
// let dimension = 25 + drand48() * 30
imageView.isUserInteractionEnabled = true
imageView.frame = CGRect(x: 10, y: 200, width: 50, height: 50)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleImageTap))
imageView.addGestureRecognizer(tapGestureRecognizer)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = customPath().cgPath
animation.duration = 5
animation.fillMode = .forwards
animation.isRemovedOnCompletion = true
animation.repeatCount = 0.5
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
imageView.layer.add(animation, forKey: nil)
view.addSubview(imageView)
// Do any additional setup after loading the view, typically from a nib.
}
#objc func handleImageTap() {
print ("clicked")
}
func customPath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 200, y: 0))
let endpoint = CGPoint(x: 20 , y: 700)
let cp1 = CGPoint(x: 90, y: 250)
let cp2 = CGPoint(x: 180, y: 400)
path.addCurve(to: endpoint, controlPoint1: cp1, controlPoint2: cp2)
return path
}
}
I am using the following code:
let MainLayer : CAShapeLayer = {
let mainlayer = CAShapeLayer()
mainlayer.fillColor = UIColor.white.cgColor
mainlayer.shadowColor = UIColor.darkGray.cgColor
mainlayer.shadowOffset = CGSize(width: 3.0, height: 3.0)
mainlayer.shadowOpacity = 0.5
mainlayer.shadowRadius = 7
return mainlayer
}()
var LayerRect:CGRect!
var bezierPath:UIBezierPath!
let CellHeightMargin:CGFloat = 6.0
let CellWidthMargin:CGFloat = 15.0
override func viewDidLoad() {
super.viewDidLoad()
LayerRect = CGRect(x: self.bounds.origin.x, y: self.bounds.origin.y, width: UIScreen.main.bounds.width - CellWidthMargin * 2, height: self.bounds.height - CellHeightMargin * 2)
bezierPath = UIBezierPath(roundedRect: LayerRect, byRoundingCorners: [.bottomRight,.topRight], cornerRadii: CGSize(width: 5, height: 5))
MainLayer.path = bezierPath.cgPath
self.mainView.layer.insertSublayer(MainLayer, at: 0)
}
As you can see, byRoundingCorners is to bottom right and top right corners, now I want to replace it by the bottom left and bottom right with animation. How to do that?
By the following:
let ToStatusLayerPath = UIBezierPath(roundedRect: CellView.statusView.bounds, byRoundingCorners: [.bottomRight,.bottomLeft], cornerRadii: CGSize(width: 5, height: 5))
let animation = CABasicAnimation(keyPath: "path")
animation.toValue = ToStatusLayerPath.cgPath
animation.duration = 1.0;
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
statusLayer.add(animation, forKey: nil)
I have a path and a shape Layer on that I want to draw a line
let path = UIBezierPath()
path.move(to: CGPoint(x: xValue,y: yValue))
path.addLine(to: CGPoint(x: xValue, y: yValue + 300))
// create shape layer for that path
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1).cgColor
shapeLayer.lineWidth = 3
shapeLayer.path = path.cgPath
But I am also animating the drawing of the line
self.chartView.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 0.6
shapeLayer.add(animation, forKey: "MyAnimation")
Now to my question:
I also want to animate the position of my path at the meanwhile. So at the moment my line is on the x-Positon of "xValue". I want to move it animated to "xValue - 40.0". I tried it on 2 ways which both didnt work.
use UIView.animate. Problem: The x Position is changing but without an animation:
UIView.animate(withDuration: 3.0) {
var highlightFrame = shapeLayer.frame
highlightFrame.origin.x = (highlightFrame.origin.x) - 40
shapeLayer.frame = highlightFrame
}
add a animation on the shape layer. Nothing happens here
let toPoint:CGPoint = CGPoint(x: -40.0, y: 0.0)
let fromPoint:CGPoint = CGPoint.zero
let movement = CABasicAnimation(keyPath: "movement")
movement.isAdditive = true
movement.fromValue = NSValue(cgPoint: fromPoint)
movement.toValue = NSValue(cgPoint: toPoint)
movement.duration = 0.3
shapeLayer.add(movement, forKey: "move")
Anyone has another solution? Thanks for your help.
let toPoint:CGPoint = CGPoint(x: -40.0, y: 0.0)
let fromPoint:CGPoint = CGPoint.zero
let movement = CABasicAnimation(keyPath: "position") // it should be "position" not "movement"
movement.isAdditive = true
movement.fromValue = NSValue(cgPoint: fromPoint)
movement.toValue = NSValue(cgPoint: toPoint)
movement.duration = 0.3
shapeLayer.add(movement, forKey: "move")