I have implemented a LoadingLayer class that has some animating elements (2 to be exact) which are animated with CABasicAnimation. Now, this loading screen view persists throughout the whole application. Does hiding the view stop all animations ? Or let me rephrase that: Is there a lot of memory I have to be worried about or could that potentially cause lags ? I tried to use the -pauseLayer: and -resumeLayer: methods but they caused some problems due to multithreading in my application. Is it therefore ok just to hide and show the loading screen with its animations running all the time ?
https://developer.apple.com/library/content/qa/qa1673/_index.html
-(void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(void)resumeLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
Here is the swift 3 version of Nico's answer.
Hope helps!
fileprivate func pauseLayer(layer: CALayer) {
let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
layer.speed = 0.0
layer.timeOffset = pausedTime
}
fileprivate func resumeLayer(layer: CALayer) {
let pausedTime = layer.timeOffset
layer.speed = 1.0
layer.timeOffset = 0.0
layer.beginTime = 0.0
let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
layer.beginTime = timeSincePause
}
If the view is hidden (invisible or not in the interface at all), then it is not part of the render tree.
But why not simply stop the animation when the view controller in whose view this layer is embedded leaves the interface (viewDidDisappear)? Just tell the layer to removeAllAnimations. Then create the animation anew, if desired, in viewWillAppear?
Extending Nico's answer for modifying speed to any variable:
extension CALayer {
func udpate(speed newSpeed: Float) {
let newSpeed = newSpeed == 0 ? 1e-6 : newSpeed
let triggerTime = self.convertTime(CACurrentMediaTime(), from: nil)
let offset = self.timeOffset
let begin = self.beginTime
let speed = self.speed
self.beginTime = (triggerTime - offset) / Double(speed) + begin
self.timeOffset = triggerTime
self.speed = newSpeed
}
}
Use this approach, if you're willing to change layer animation speed interactively. If you want to Completely stop the layer, you should use Nico's answer, although using this method with speed=0.0 will actually take about 11 days for animations to finish, and in 1hour animation will progress about 0.0036
Related
I created a function with Core Animation that animate the layer height from 0 to N and it's delayable.
extension CALayer {
func animate(to height: CGFloat, duration: Double, delay: Double) {
let animation: CABasicAnimation = .init(keyPath: "bounds.size.height")
animation.fromValue = 0
animation.toValue = height
animation.beginTime = CACurrentMediaTime() + delay
animation.duration: duration
animation.timingFunction = .init(name: kCAMediaTimingFunctionEaseInEaseOut)
// I want to improvement this part.
//
// self.isHidden = true
//
// DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
// self.isHidden = false
// }
self.bounds.size.height = height
self.add(animation, forKey: "bounds.size.height")
}
}
And the layer does transform well during animation, but it returns to original height before begin time and after finish. So I had to set isHidden of layer according to time to delay.
But I don't think it's a safe way. So I want to improvement that code.
What do you usually do in this case?
Try setting animation.fillMode = .backwards. I think that will make the animation will apply its fromValue to the layer until the animation's beginTime is reached.
My code is below. How can I speed slow when animation has stop?
extension UIView{
func rotate() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.fromValue = 0.0
rotation.toValue = 25
rotation.duration = 1.5
rotation.isCumulative = true
rotation.repeatCount = 1
self.layer.add(rotation, forKey: "rotationAnimation")
}
}
Please find following details and add below line in your code,
rotation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
EaseInOut An ease-in ease-out curve causes the animation to begin
slowly, accelerate through the middle of its duration, and then slow
again before completing. This is the default curve for most
animations.
EaseIn An ease-in curve causes the animation to begin slowly, and then
speed up as it progresses.
EaseOut An ease-out curve causes the animation to begin quickly, and
then slow down as it completes.
Hope this helps to you and let me know in case of any queries.
You can use self.layer.speed to decrease animation speed smoothly
You can do it like this
Note: You need to do some modification here it is not tested in xcode
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
self.layer.timeOffset = self.layer.convertTime(CACurrentMediaTime(), from: nil)
if self.layer.speed == 0 { timer.invalidate() }
self.layer.beginTime = CACurrentMediaTime()
self.layer.speed -= 0.5
}
timer.fire()
Hope it is helpful
Basically I just add a simple rotate animation on an imageView which is a subview of UICollectionViewCell, the animation works fine, but when I scroll the collection view and scroll back, the animation just stoped. How to solve this problem?
This is what I added to the imageView.
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Double.pi
rotationAnimation.duration = 2.0
rotationAnimation.repeatCount = .infinity
view.layer.add(rotationAnimation, forKey: nil)
To avoid scrolling's influence, need to use Runloop and its commonModes, making animation with CADisplayLink can do it:
private var _displayLinkForRotation:CADisplayLink?
var displayLinkForRotation:CADisplayLink {
get{
if _displayLinkForRotation == nil {
_displayLinkForRotation = CADisplayLink(target: self, selector: #selector(excuteAnimations))
_displayLinkForRotation?.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)
}
return _displayLinkForRotation!
}
}
func excuteAnimations() {
//This function will be called 60 times per second.
//According to your question, you have 2 seconds to rotate the view to 180 angle. So we rotate 90 angle per second here.
//self.view could be replaced by another view you want to rotate.
self.view.transform = self.view.transform.rotated(by: 90.0 / 60.0 / 180.0 * CGFloat.pi)
let angle = atan2(self.view.transform.b, self.view.transform.a) * (45.0/atan(1.0))
if (round(angle) >= 180.0) { //Stop when rotated 180 angle
self.displayLinkForRotation.isPaused = true
}
}
To start animation:
self.displayLinkForRotation.isPaused = false
To destroy it:
self.displayLinkForRotation.invalidate()
_displayLinkForRotation = nil
Because you have not added key for animation, which you used when initialising rotationAnimation
change line
view.layer.add(rotationAnimation, forKey: nil)
to
view.layer.add(rotationAnimation, forKey: "transform.rotation")
Hope it helps
I was trying to do a "flip x animation" for a UIButton. I have seen some documentation on how to do this animation but i couldn't find how to do it with repetition. For example, i want to flip the image of the UIButton and change it after a time interval of 5 sec with repetition.
Any help? Thanks.
You can use CAAnimationGroup to group an array of animations. Total animation for whole animationGroup is 6.0, flip animation takes place for 1.0 second. so remaining 5.0 seconds act as delay here.
EDIT: CATransaction.setCompletionBlock can be used but it will only call completion after all animations are completed. So now the flip will occur only for one time and on completion you will receive completion call, and update image view and initiate flip animation again.
Like this
imageView.flip {
// update imageView and call it again.
}
extension UIView {
func flip(completion: #escaping ()->()) {
CATransaction.begin()
CATransaction.setCompletionBlock {
// completion code here
completion()
}
let animationGroup = CAAnimationGroup()
animationGroup.duration = 6.0
animationGroup.repeatCount = 1
let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
let animation = CABasicAnimation(keyPath: "transform")
animation.duration = 1.0
animation.autoreverses = true
animation.fromValue = CATransform3DIdentity
animation.toValue = NSValue(caTransform3D: CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0))
animation.timingFunction = easeInOut
animationGroup.animations = [animation]
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
layer.add(animationGroup, forKey: "flip")
CATransaction.commit()
}
}
How do I make an object, for example an image, rotate forever?
UIView.animateWithDuration(2.0, animations: ({
self.Cube.transform = CGAffineTransformMakeRotation(320)
}))
Does anyone have a solution, would really appreciate some help.
Try this function to rotate a layer forever.
func rotateLayer(layer:CALayer,duration:CFTimeInterval){
let animation = CABasicAnimation(keyPath: "transform.rotation");
animation.fromValue = 0;
animation.duration = duration;
animation.toValue = CGFloat(M_PI*2);
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear);
animation.repeatCount = Float.infinity;
layer.addAnimation(animation, forKey: "rotation");
}
usage (the duration is time it takes the layer to rotate 360 degress):
rotateLayer(subView.layer, duration: 2);