I'm trying to animate the bounds of a UIButton using Facebook pop POPSpringAnimation, but I can't find what I should use in swift in replace of NSValue.valueWithCGRect
This is what I'm trying to do:
#IBAction func buttonTapped(sender : UIButton) {
var springAnimation = POPSpringAnimation()
springAnimation.property = POPAnimatableProperty.propertyWithName(kPOPLayerBounds) as POPAnimatableProperty
springAnimation.springBounciness = 12.0
springAnimation.springSpeed = 10.0
springAnimation.toValue = NSValue.valueWithCGRect(newBounds)
shutterButton.pop_addAnimation(springAnimation, forKey: "size")
}
This will compile:
let rect = CGRect(x: 0,y: 0,width: 1,height: 1)
let val = NSValue(CGRect: rect)
This is "initializer syntax"
Here's my solution:
func scaleView(view: UIView, magnitude: CGFloat) {
var springAnimation = POPSpringAnimation()
springAnimation.property = POPAnimatableProperty.propertyWithName(kPOPViewScaleXY) as POPAnimatableProperty
springAnimation.springBounciness = 12.0
springAnimation.springSpeed = 10.0
let newRect = CGRect(x: (view.bounds.origin.x + (view.bounds.size.width / 2.0)) - ((view.bounds.size.width / 2.0) * magnitude), y: (view.bounds.origin.y + (view.bounds.size.height / 2.0)) - ((view.bounds.size.height / 2.0) * magnitude), width: view.bounds.size.width * magnitude, height: view.bounds.size.height * magnitude)
springAnimation.fromValue = NSValue(CGRect: view.bounds)
springAnimation.toValue = NSValue(CGRect: newRect)
view.pop_addAnimation(springAnimation, forKey: "size")
}
In Swift:
let hover = CABasicAnimation(keyPath: "position")
hover.additive = true
hover.fromValue = NSValue(CGPoint: CGPointZero)
hover.toValue = NSValue(CGPoint: CGPointMake(0.0, -10.0))
hover.autoreverses = true
hover.duration = 0.2
hover.repeatCount = Float(Int.max)
button.layer.addAnimation(hover, forKey: "myHoverAnimation")
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")
func addUnderlineForSelectedSegment() {
removeBorder()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 3.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height + 10.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
// underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
print("underline:\(underline)")
underline.backgroundColor = .red
underline.tag = 1
self.addSubview(underline)
didAddSubview(underline)
}
I got a bug of the same kind with you.
It's work in older version but doesn't work in currently iOS 13.
Spend all day for fixing this stupid thing, and one line fixing everything.
Try to set clipsToBounds to false after removeBorder().
func addUnderlineForSelectedSegment(){
removeBorder()
clipsToBounds = false
let underlineWidth: CGFloat = Size.Screen.width / CGFloat(numberOfSegments)
let underlineHeight: CGFloat = 1.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = bounds.size.height - 1.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = Color.default80.withAlphaComponent(0.8)
underline.tag = 1
addSubview(underline)
}
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!)
}
}
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)