Overlapping with CABasicAnimation Scaling - swift

I have a collection view where the first cell needs to be animated with scaling. The standard cabasic animation's behaviour goes under neighbouring cell but I need to find a way so the scaling animation overlaps neighbouring cell. I've tried to put a clear view on a cell and animate it, so it would've been at the top of view hierarchy but it didn't help. Is there any assumptions how it can be achieved?
class func pulse(view: UIView, sizeMultiplier: Float, duration: TimeInterval, repeatCount: Float = 1.0) {
let pulseAnimation = CABasicAnimation(keyPath: "transform.scale")
pulseAnimation.duration = duration
pulseAnimation.toValue = NSNumber(value: sizeMultiplier)
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = repeatCount
view.layer.add(pulseAnimation, forKey: nil)
}

Related

CAAnimationGroup timing

This is strange to me
For the following code (where myView is a coloured UIView on the storyboard) the timing seems off.
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let round = CABasicAnimation(keyPath: "cornerRadius")
round.fromValue = 0.0
round.toValue = 50.0
round.duration = 2.0
round.beginTime = 0.0
round.fillMode = CAMediaTimingFillMode.backwards
round.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
myView.layer.cornerRadius = 50.0
let scaleDown = CABasicAnimation(keyPath: "transform.scale")
scaleDown.fromValue = 1.0
scaleDown.toValue = 0.25
scaleDown.beginTime = 2.0
scaleDown.duration = 2.0
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = .pi/10.0
rotate.toValue = 0.0
rotate.beginTime = 4.0
rotate.duration = 2.0
let hideAnimation = CABasicAnimation(keyPath: "hidden")
hideAnimation.fromValue = NSNumber(value: 1)
hideAnimation.toValue = NSNumber(value: 0)
hideAnimation.beginTime = 4.0
hideAnimation.duration = 2.0
let imageGroupAnimations = CAAnimationGroup()
imageGroupAnimations.animations = [round, scaleDown, rotate, hideAnimation]
imageGroupAnimations.duration = 6.0
imageGroupAnimations.repeatCount = 1
imageGroupAnimations.beginTime = 0.0
myView.layer.add(imageGroupAnimations, forKey: nil)
}
}
The duration of the animation group is 6.0, so I would expect the shape to be hidden (since I expect it to be hidden from 4 to 4 + 2 (6.0) seconds.
However, the shape appears and finishes off the animation (finish rotating). I've tried using CACurrentMediaTime() in front of every beginTime - but this also doesn't have the expected result.
Expected result:
0-2 seconds animate corner radius change
2-4 seconds animate scale transformation
4-6 seconds animate rotation (hidden from the user)
How can I make the shape disappear from 4 - 6 seconds?
If you want to show fade-out effect on view, use
let hideAnimation = CABasicAnimation(keyPath: "opacity")
Instead of
let hideAnimation = CABasicAnimation(keyPath: "hidden") //bad!
If you don't want animation to reset your values after finish, simply add
imageGroupAnimations.fillMode = .forwards
imageGroupAnimations.isRemovedOnCompletion = false
to your CAAnimationGroup
The duration is not how long something stays true — it is how long it takes to perform a change.
So if you want the view to vanish suddenly, obviously you need to change hideAnimation.duration = 2.0 so that the duration is very short, say, 0.01.
This is a hideAnimation where the view turns invisible suddenly at the 4th second of the animation, and stays invisible until the end of the animation:
let hideAnimation = CABasicAnimation(keyPath: "hidden")
hideAnimation.fromValue = false
hideAnimation.toValue = true
hideAnimation.beginTime = 4.0
hideAnimation.duration = 0.01
hideAnimation.fillMode = .forwards
But I did not make any other changes because I still do not understand the complete effect that you want...

Why is my Swift button animation growing back to full size?

What I expect: That when I press down on the button it shrinks to 75% and stays at its 75% shrunken size until I let go.
What is happening: The buttons shrinks to 75%, but as soon as it finishes the duration of the animation, while my finger is still pressed on the button, it grows back to its original value.
I am called the following when a button is "Touched Down".
import Foundation
import UIKit
extension UIButton {
func shrink() {
let shrink = CABasicAnimation(keyPath: "transform.scale")
shrink.fromValue = 1.0
shrink.toValue = 0.75
shrink.duration = 0.5
shrink.isRemovedOnCompletion = false
layer.add(shrink, forKey: nil)
}
}
The size resets because CABasicAnimation doesn't update the layer's underlying value, only the presentation layer. So when the animation ends, it goes back to using the underlying value.
The best way to deal with this is to set the transform scale before the animation starts so that it is the correct value at the end.
func shrink() {
transform = CGAffineTransform(scaleX: 0.75, y: 0.75) // Set final state
let shrink = CABasicAnimation(keyPath: "transform.scale")
shrink.fromValue = 1.0
shrink.toValue = 0.75
shrink.duration = 0.5
layer.add(shrink, forKey: nil)
}
You can also set the fillMode to .forwards and isRemovedOnCompletion to false, which will keep the presentation layer changes in place, but this doesn't update the layer's actual scale, so the button will keep the original scale for tap detection. (You need to set the transform's scale as above to correct it.)
func shrink() {
let shrink = CABasicAnimation(keyPath: "transform.scale")
shrink.fromValue = 1.0
shrink.toValue = 0.75
shrink.duration = 0.5
shrink.isRemovedOnCompletion = false
shrink.fillMode = .forwards
layer.add(shrink, forKey: nil)
}
You can also use a view animation which will have the same visual effect, but also update the scale:
UIView.animate(withDuration: 0.5) { [weak self] in
self?.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
}

Animation stops when UIView reappears on scrolling UITableView

I have an spinning UIView to show progress in each cell of a UITableView and I am using this function to animate UIViews:
func rotate360Degrees(duration: CFTimeInterval = 1.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat.pi * 2
rotateAnimation.duration = duration
rotateAnimation.repeatCount = Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
It works fine when the cells appears first but when you scroll the UITableView and the cells disappear and after that they shows up by scrolling again, their animations are stopped. I tried calling the method for them again after reappearing but it didn't work. what is wrong with my code?
UITableViewCell objects are reusable and you need to restore the animation in prepareForReuse: or tableView(_:willDisplay:forRowAt:) method.
func getRotate360DegreesAnimation(duration: CFTimeInterval = 1.0) -> CABasicAnimation {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = .pi * 2
rotateAnimation.duration = duration
rotateAnimation.repeatCount = .infinity
return rotateAnimation
}
func restoreAnimation() {
let animation = getRotate360DegreesAnimation()
layer.removeAllAnimations()
layer.add(animation, forKey: nil)
}

Adding CAShapeLayer to UIButton Not Working

I am working on a Swift project and creating a circle around a UIButton, using CAShapeLayer and creating a circular UIBezeir path.The problem is that if I add this CAShapLayer as a sublayer to UIbutton,It does not work. However adding this sublayer on UiView creates the circle.
Below is my code.
let ovalPath = UIBezierPath(arcCenter: lockButton.center, radius:
CGFloat(lockButton.frame.size.width/2), startAngle: CGFloat(startAngle), endAngle: CGFloat(sentAngel), clockwise: true)
UIColor.grayColor().setFill()
ovalPath.fill()
let circleLayer = CAShapeLayer()
circleLayer.path = ovalPath.CGPath
view.layer.addSublayer(circleLayer)
circleLayer.strokeColor = UIColor.blueColor().CGColor
circleLayer.fillColor = UIColor.clearColor().CGColor
circleLayer.lineWidth = 10.0
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = 0.5
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = 1
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = 3.0
// Do the actual animation
circleLayer.addAnimation(animation, forKey: "animateCircle")
Can anyone suggest me, if we can add CAShapeLayer to UIButton?
Thanks
One very obvious problem with your code is that your circleLayer has no frame. It's kind of amazing that anything appears at all. You need to set the frame of the circleLayer so that it has size and so that it is positioned correctly with respect to its superlayer.
To resolve this problem you need to remove UIButton background color and set it on the default value.

CABasicAnimation reverse(backwards)

I'm a bit struggling with this simple line animation. I figured out how to pause it, but what I need is to be able to reverse animation back to starting point from the moment I call function resetAnimation().
let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
let pathLayer = CAShapeLayer()
func lineAnimation() {
let path = UIBezierPath()
let screenWidth = self.view.bounds.width
let screenHeight = self.view.bounds.height
path.moveToPoint(CGPointMake(screenWidth, screenHeight / 2))
path.addLineToPoint(CGPointMake(screenWidth - screenWidth, screenHeight / 2))
self.pathLayer.frame = self.view.bounds
self.pathLayer.path = path.CGPath
self.pathLayer.strokeColor = UIColor.whiteColor().CGColor
self.pathLayer.fillColor = nil
self.pathLayer.lineWidth = 3.0
self.pathLayer.lineCap = kCALineCapRound
self.pathLayer.speed = 1
self.view.layer.addSublayer(pathLayer)
self.pathAnimation.duration = 5.0
self.pathAnimation.fromValue = 0.0
self.pathAnimation.toValue = 1.0
pathLayer.addAnimation(pathAnimation, forKey: "animate")
}
func pauseAnimation() {
let pausedTime = pathLayer.convertTime(CACurrentMediaTime(), fromLayer: nil)
pathLayer.speed = 0
pathLayer.timeOffset = pausedTime
}
func resetAnimation() {
}
You just need to create a new animation and remove the old one. Your starting point for the new animation will be the current value of that property in your presentation layer. I'd also recommend setting the frame of your shape layer to the bounds of the actual bezier shape instead of the entire view - its a good habit to be in when you start moving things around and scaling/rotating/etc. Otherwise you're gonna be faced with a bunch of funky conversions or anchor point changes.
Here's what I'd do:
let pathLayer = CAShapeLayer()
// first, separate your drawing code from your animation code.
// this way you can call animations without instantiating new objects
func drawLine() {
let path = UIBezierPath()
// draw your path with no position translation.. move the layer
path.moveToPoint(CGPointMake(view.bounds.width, 0))
path.addLineToPoint(CGPointMake(0, 0))
pathLayer.frame = path.bounds
// this line sets the position of the layer appropriately
pathLayer.position = view.bounds.width - pathLayer.bounds.width / 2
pathLayer.path = path.CGPath
pathLayer.strokeColor = UIColor.whiteColor().CGColor
pathLayer.fillColor = nil
pathLayer.lineWidth = 3.0
pathLayer.lineCap = kCALineCapRound
view.layer.addSublayer(pathLayer)
}
func lineAnimation() {
let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 5.0
pathAnimation.fromValue = 0.0
pathAnimation.toValue = 1.0
pathLayer.addAnimation(pathAnimation, forKey: "strokeEnd")
}
func reverseAnimation() {
let revAnimation = CABasicAnimation(keyPath: "strokeEnd")
revAnimation.duration = 5.0
// every Core Animation has a 'presentation layer' that contains the animated changes
revAnimation.fromValue = pathLayer.presentationLayer()?.strokeEnd
revAnimation.toValue = 0.0
pathLayer.removeAllAnimations()
pathLayer.addAnimation(revAnimation, forKey: "strokeEnd")
}
Keep in mind you'll also want to set your properties so you retain the end values of the animation.
You could also use the autoreverses property of CAMediaTiming protocol, which is complied by CABasicAnimation