Swift: Reset frame of a UIView to its constraints after animation - ios

I am setting a UIImageView using constraints added programmatically in viewDidLoad.
Later on, after the user interacts with some buttons on the screen, I move the view by changing its frame using CAKeyframeAnimation with an array ofCGRect` values for the movement.
Now later in the app lifecycle, I'd like the UIImageView returned to where it was originally placed (using the constraints that I haven't changed). Is there a way to reset the frames to their initial constraints?

It's possible to revert to the original values using removeAnimation, doc here. So if use set your animation (let's say move) like:
let animation = CAKeyframeAnimation()
animation.keyPath = "position.y"
animation.values = [0, 200]
animation.keyTimes = [0, 1]
animation.duration = 1
animation.isAdditive = true
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
innerView.layer.add(animation, forKey: "move")
then whenever you need to revert, you might call:
innerView.layer.removeAnimation(forKey: "move")

Related

CoreAnimation resets to initial value after animation completes [duplicate]

I am using CABasicAnimation to move and resize an image view. I want the image view to be added to the superview, animate, and then be removed from the superview.
In order to achieve that I am listening for delegate call of my CAAnimationGroup, and as soon as it gets called I remove the image view from the superview.
The problem is that sometimes the image blinks in the initial location before being removed from the superview. What's the best way to avoid this behavior?
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.animations = [NSArray arrayWithObjects:moveAnim, scaleAnim, opacityAnim, nil];
animGroup.duration = .5;
animGroup.delegate = self;
[imageView.layer addAnimation:animGroup forKey:nil];
When you add an animation to a layer, the animation does not change the layer's properties. Instead, the system creates a copy of the layer. The original layer is called the model layer, and the duplicate is called the presentation layer. The presentation layer's properties change as the animation progresses, but the model layer's properties stay unchanged.
When you remove the animation, the system destroys the presentation layer, leaving only the model layer, and the model layer's properties then control how the layer is drawn. So if the model layer's properties don't match the final animated values of the presentation layer's properties, the layer will instantly reset to its appearance before the animation.
To fix this, you need to set the model layer's properties to the final values of the animation, and then add the animation to the layer. You want to do it in this order because changing a layer property can add an implicit animation for the property, which would conflict with the animation you want to explicitly add. You want to make sure your explicit animation overrides the implicit animation.
So how do you do all this? The basic recipe looks like this:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [NSValue valueWithCGPoint:myLayer.position];
layer.position = newPosition; // HERE I UPDATE THE MODEL LAYER'S PROPERTY
animation.toValue = [NSValue valueWithCGPoint:myLayer.position];
animation.duration = .5;
[myLayer addAnimation:animation forKey:animation.keyPath];
I haven't used an animation group so I don't know exactly what you might need to change. I just add each animation separately to the layer.
I also find it easier to use the +[CATransaction setCompletionBlock:] method to set a completion handler for one or several animations, instead of trying to use an animation's delegate. You set the transaction's completion block, then add the animations:
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
[self.imageView removeFromSuperview];
}];
[self addPositionAnimation];
[self addScaleAnimation];
[self addOpacityAnimation];
} [CATransaction commit];
CAAnimations are removed automatically when complete. There is a property removedOnCompletion that controls this. You should set that to NO.
Additionally, there is something known as the fillMode which controls the animation's behavior before and after its duration. This is a property declared on CAMediaTiming (which CAAnimation conforms to). You should set this to kCAFillModeForwards.
With both of these changes the animation should persist after it's complete. However, I don't know if you need to change these on the group, or on the individual animations within the group, or both.
Heres an example in Swift that may help someone
It's an animation on a gradient layer. It's animating the .locations property.
The critical point as #robMayoff answer explains fully is that:
Surprisingly, when you do a layer animation, you actually set the final value, first, before you start the animation!
The following is a good example because the animation repeats endlessly.
When the animation repeats endlessly, you will see occasionally a "flash" between animations, if you make the classic mistake of "forgetting to set the value before you animate it!"
var previousLocations: [NSNumber] = []
...
func flexTheColors() { // "flex" the color bands randomly
let oldValues = previousTargetLocations
let newValues = randomLocations()
previousTargetLocations = newValues
// IN FACT, ACTUALLY "SET THE VALUES, BEFORE ANIMATING!"
theLayer.locations = newValues
// AND NOW ANIMATE:
CATransaction.begin()
// and by the way, this is how you endlessly animate:
CATransaction.setCompletionBlock{ [weak self] in
if self == nil { return }
self?.animeFlexColorsEndless()
}
let a = CABasicAnimation(keyPath: "locations")
a.isCumulative = false
a.autoreverses = false
a.isRemovedOnCompletion = true
a.repeatCount = 0
a.fromValue = oldValues
a.toValue = newValues
a.duration = (2.0...4.0).random()
theLayer.add(a, forKey: nil)
CATransaction.commit()
}
The following may help clarify something for new programmers. Note that in my code I do this:
// IN FACT, ACTUALLY "SET THE VALUES, BEFORE ANIMATING!"
theLayer.locations = newValues
// AND NOW ANIMATE:
CATransaction.begin()
...set up the animation...
CATransaction.commit()
however in the code example in the other answer, it's like this:
CATransaction.begin()
...set up the animation...
// IN FACT, ACTUALLY "SET THE VALUES, BEFORE ANIMATING!"
theLayer.locations = newValues
CATransaction.commit()
Regarding the position of the line of code where you "set the values, before animating!" ..
It's actually perfectly OK to have that line actually "inside" the begin-commit lines of code. So long as you do it before the .commit().
I only mention this as it may confuse new animators.

CABasicAnimation duration & keeping layer position

I'm trying to make very basic animation for UIButton. The goal is rotate layer 180 degrees. Here is my animation code which is called from beginTrackingWithTouch:
private func rotateButton() {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.timingFunction = CAMediaTimingFunction(name: "easeIn")
rotationAnimation.toValue = M_PI
rotationAnimation.duration = CollapseButton.kCollapseButtonAnimationDuration
rotationAnimation.repeatCount = 1
rotationAnimation.cumulative = true
layer.addAnimation(rotationAnimation, forKey: "rotationAnimation")
}
Now I'd like to add collapsing view animation when tapping this button. In my VC:
__weak __typeof(self) weakSelf = self;
[UIView animateWithDuration:CollapseButton.kCollapseButtonAnimationDuration animations:^{
CGRect currentFrame = weakSelf.frame;
currentFrame.size.height = 20;
weakSelf.frame = currentFrame;
}];
I have 2 questions:
After button finishes its animation it resets layer position. So, if arrow were showing top, it animated to showing down and finally resets to top. How can I preserve layer orientation?
As you can see animation duration and timing functions are the same. For the reason I cannot understand UIView animates much slower. Any ideas?
Core animation is strange. The animation creates a "presentation layer" that generates the appearance of the change you are animating, but does not actually change the property in question.
In order to get your animation to finish with the object at the end state, you should set both a fromValue (at the starting setting) and a toValue, and then set the property to it's ending value after submitting the animation:
private func rotateButton() {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.timingFunction = CAMediaTimingFunction(name: "easeIn")
rotationAnimation.fromValue = 0.0 //<<--- NEW CODE
rotationAnimation.toValue = M_PI
rotationAnimation.duration = CollapseButton.kCollapseButtonAnimationDuration
rotationAnimation.repeatCount = 1
rotationAnimation.cumulative = true
layer.addAnimation(rotationAnimation, forKey: "rotationAnimation")
layer.transform = CATransformMakeRotation(M_PI) //<<--- NEW CODE
}
You can also set the animation's removeWhenFinished property to true, but that has other complications.
BTW, you should not try to manipulate the frame of a view that has a non-identity transform. Instead, set the scale on the view's transform.
I'm not sure why the 2 animations are taking different amounts of time. I do notice that you are setting the CAAnimation's timing function to easeIn, but leaving the UIView's timing function as the default (ease-in, ease-out.) That will create animations that don't look the same. You should probably set your view animation to use easeIn timing as well. (To do that you'll need to use the longer form animateWithDuration:delay:options:animations:completion:)

CABasicAnimation change startingpoint to go forward and backwards

I'm trying to make a small ui controlled animation (as described in this answer) and continue this animation after rotation. Due to autolayout issues I have to remove and add the whole layer on autorotate.
This works so far. My problem is, as I want continue the animation from the same position it was left off, it won't go anywhere prior to the state it was the moment it rotated.
e.g. the slider is at 0.5. The animation was added again (due to removal) and I've set the timeOffset corresponding to 0.5. The animation will go forward, but not backward.
I create my animation via:
let animatePhase = CABasicAnimation(keyPath: "lineDashPhase")
animatePhase.byValue = phaseLength
animatePhase.duration = 1.0
animatePhase.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animatePhase.repeatCount = Float.infinity
lineLayer.addAnimation(animatePhase, forKey: "marching ants")
lineLayer.speed = 0.0
lineLayer.timeOffset = 0.0;
lineLayer.beginTime = 0.0;
lineLayer is a CAShapeLayer.
On `layoutSubviews' I remove and recreate the layer.
What am I doing wrong?
Thanks

Create smooth animation with UIImageView animation

I'm currently animating between 4 images like this:
UIImageView *tom3BeforeImage;
tom3Images = [NSArray arrayWithObjects: [UIImage imageNamed:#"floortom_before1.png"],[UIImage imageNamed:#"floortom_before2.png"],[UIImage imageNamed:#"floortom_before3.png"],[UIImage imageNamed:#"floortom_before4.png"], nil ];
tom3BeforeImage.animationImages = tom3Images;
tom3BeforeImage.animationDuration = 0.75;
[tom3BeforeImage startAnimating];
It works fine, except that the animation is choppy between the images. I need the duration to be exactly .75 seconds, so speeding it up is not an option.
What's the best way to have the animation be smoother between the images, kind of like blending between each image change?
Thanks!
If you're using frame based UIImageView animation, and the animation must be .75 seconds, then the only way I know of to make it smoother is to create more frames. Try 30 frames/second, or about 22 frames. That should give very smooth motion.
If you want some sort of cross-dissolve between frames then you won't be able to use UIView frame animation. you'll have to use UIView block animation (using animateWithDuration:animations: or its cousins.)
You could create a sequence of cross-dissolves between your frames where the total duration of the sequence is .75 seconds. Have each transition trigger the next transition in it's completion block.
Something like this:
You'll need 2 image views, stacked on top of each other. You'll fade one out and the other in at the same time. You'll need to set the opaque flag to NO on both.
Lets call them tom3BeforeImage1 and tom3BeforeImage2
Add an int instance variable imageCount and make your array of images, tom3Images, and instance variable as well:
- (void) animateImages;
{
CGFloat duration = .75 / ([tom3Images count] -1);
//Start with the current image fully visible in tom3BeforeImage1
tom3BeforeImage1.image = tom3Images[imageCount];
tom3BeforeImage1.alpha = 1.0;
//Get the next image ready, at alpha 0, in tom3BeforeImage2
tom3BeforeImage2.image = tom3Images[imageCount+1];
tom3BeforeImage2.alpha = 0;
imageCount++
[UIView animateWithDuration: duration
delay: 0
options: UIViewAnimationOptionCurveLinear
animations:
^{
//Fade out the current image
tom3BeforeImage1.alpha = 0.0;
//Fade in the new image
tom3BeforeImage2.alpha = 1.0;
}
completion:
^{
//When the current animation step completes, trigger the method again.
if (imageCount < [tom3Images count] -1)
[self animateImages];
}
];
}
Note that I banged out the code above in the forum editor without having had enough coffee. It likely contains syntax errors, and may even have logic problems. This is just to get you thinking about how to do it.
Edit #2:
I'm not sure why, but I decided to flesh this out into a full-blown example project. The code above works passably well after debugging, but since it's fading one image out at the same time it's fading another one in, the background behind both image views shows through.
I reworked it to have logic that only fades the top image in and out. It puts the first frame in the top image view and the second frame in the bottom image view, then fades out the top image view.
The project is up on github, called Animate-Img. (link)
Then it installs the third frame in the top image view and fades it IN,
Then it installs the 4th fame in the bottom image view and fades out the top to expose the bottom, etc, etc.
I ended up creating a generalized method
- (void) animateImagesWithDuration: (CGFloat) totalDuration
reverse: (BOOL) reverse
crossfade: (BOOL) doCrossfade
withCompletionBlock: (void (^)(void)) completionBlock;
It will animate a set of images, into a pair of image views, optionally reversing the animation once it's done. It takes a completion block that gets called once the animation is finished.
The animate button actually calls a method that repeats the whole animation sequence. It's currently set to only run it once, but changing a constant will make the program repeat the whole sequence, if desired.
I do had the requirement to have animation with array of images. Initially when i used animationImages property of imageview, I got the desired animation but the transition between the images were not smooth, I then used CAKeyframeAnimation to achieve the smooth transition, the catch is to use timingFunction along with correct calculationMode. I am not sure this is the exact the answer for the question but this is one way to make the animation smoother,
below is the code for that
For more info on calculation mode please see Apple Documentation
- (void) animate
{
NSMutableArray * imageArray = [[NSMutableArray alloc] init];
int imageCount = 8;
for (int i=0; i<=imageCount; i++) {
[imageArray addObject:(id)[UIImage imageNamed:[NSString stringWithFormat:#“image%d”,i]].CGImage];
}
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animation.calculationMode = kCAAnimationLinear;// Make sure this is kCAAnimationLinear or kCAAnimationCubic other mode doesnt consider the timing function
animation.duration = 8.0;
animation.values = imageArray;
animation.repeatCount = 1; // Change it for repetition
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards; // To keep the last frame when animation ends
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[imageView.layer addAnimation:animation forKey:#"animation"];
}
UPDATE- Swift 3
func animate() {
var imageArray = [CGImage]()
let imageCount: Int = 3
for i in 0...imageCount {
imageArray.append((UIImage(named: String(format:"image\(i)"))?.cgImage!)!)
}
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.calculationMode = kCAAnimationLinear
// Make sure this is kCAAnimationLinear or kCAAnimationCubic other mode doesnt consider the timing function
animation.duration = CFTimeInterval(floatLiteral: 8.0)
animation.values = imageArray
animation.repeatCount = 1
// Change it for repetition
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
// To keep the last frame when animation ends
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animImageView.layer.add(animation, forKey: "animation")
}

CABasicAnimation resets to initial value after animation completes

I am rotating a CALayer and trying to stop it at its final position after animation is completed.
But after animation completes it resets to its initial position.
(xcode docs explicitly say that the animation will not update the value of the property.)
any suggestions how to achieve this.
Here's the answer, it's a combination of my answer and Krishnan's.
cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;
The default value is kCAFillModeRemoved. (Which is the reset behavior you're seeing.)
The problem with removedOnCompletion is the UI element does not allow user interaction.
I technique is to set the FROM value in the animation and the TO value on the object.
The animation will auto fill the TO value before it starts, and when it's removed will leave the object at it's correct state.
// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: #"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;
alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;
[self.view.layer addAnimation: alphaAnimation forKey: #"fade"];
Core animation maintains two layer hierarchies: the model layer and the presentation layer. When the animation is in progress, the model layer is actually intact and keeps it initial value. By default, the animation is removed once the it's completed. Then the presentation layer falls back to the value of the model layer.
Simply setting removedOnCompletion to NO means the animation won't be removed and wastes memory. In addition, the model layer and the presentation layer won't be synchronous any more, which may lead to potential bugs.
So it would be a better solution to update the property directly on the model layer to the final value.
self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
If there's any implicit animation caused by the first line of above code, try to turn if off:
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
Reference:
Animations Explained by objc.io.
"iOS 7 Programming Pushing the Limits" by Rob Napier and Mugunth Kumar.
Set the following property:
animationObject.removedOnCompletion = NO;
You can simply set the key of CABasicAnimation to position when you add it to the layer. By doing this, it will override implicit animation done on the position for the current pass in the run loop.
CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);
someLayer.position = endPosition; // Implicit animation for position
CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:#"position.y"];
animation.fromValue = #(someLayer.position.y);
animation.toValue = #(someLayer.position.y + yOffset);
[someLayer addAnimation:animation forKey:#"position"]; // The explicit animation 'animation' override implicit animation
You can have more information on 2011 Apple WWDC Video Session 421 - Core Animation Essentials (middle of the video)
just put it inside your code
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.fillMode = kCAFillModeForwards;
theGroup.removedOnCompletion = NO;
A CALayer has a model layer and a presentation layer. During an animation, the presentation layer updates independently of the model. When the animation is complete, the presentation layer is updated with the value from the model. If you want to avoid a jarring jump after the animation ends, the key is to keep the two layers in sync.
If you know the end value, you can just set the model directly.
self.view.layer.opacity = 1;
But if you have an animation where you don't know the end position (e.g. a slow fade that the user can pause and then reverse), then you can query the presentation layer directly to find the current value, and then update the model.
NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:#"opacity"];
[self.layer setValue:opacity forKeyPath:#"opacity"];
Pulling the value from the presentation layer is also particularly useful for scaling or rotation keypaths. (e.g. transform.scale, transform.rotation)
So my problem was that I was trying to rotate an object on pan gesture and so I had multiple identical animations on each move. I had both fillMode = kCAFillModeForwards and isRemovedOnCompletion = false but it didn't help. In my case, I had to make sure that the animation key is different each time I add a new animation:
let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = CAMediaTimingFillMode.forwards
head.layer.add(rotate, forKey: "rotate\(angle)")
This works:
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")
Core animation shows a 'presentation layer' atop your normal layer during the animation. So set the opacity (or whatever) to what you want to be seen when the animation finishes and the presentation layer goes away. Do this on the line before you add the animation to avoid a flicker when it completes.
If you want to have a delay, do the following:
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.
someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")
Finally, if you use 'removedOnCompletion = false' it'll leak CAAnimations until the layer is eventually disposed - avoid.
Without using the removedOnCompletion
You can try this technique:
self.animateOnX(item: shapeLayer)
func animateOnX(item:CAShapeLayer)
{
let endPostion = CGPoint(x: 200, y: 0)
let pathAnimation = CABasicAnimation(keyPath: "position")
//
pathAnimation.duration = 20
pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
pathAnimation.toValue = endPostion
pathAnimation.fillMode = kCAFillModeBoth
item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes
item.add(pathAnimation, forKey: nil)
}
Simply setting fillMode and removedOnCompletion didn't work for me. I solved the problem by setting all of the properties below to the CABasicAnimation object:
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:#"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];
This code transforms myView to 85% of its size (3rd dimension unaltered).
#Leslie Godwin's answer is not really good, "self.view.layer.opacity = 1;" is done immediately (it takes about one second), please fix alphaAnimation.duration to 10.0, if you have doubts.
You have to remove this line.
So, when you fix fillMode to kCAFillModeForwards and removedOnCompletion to NO, you let the animation remains in the layer. If you fix the animation delegate and try something like:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
[theLayer removeAllAnimations];
}
...the layer restores immediately at the moment you execute this line. It's what we wanted to avoid.
You must fix the layer property before remove the animation from it. Try this:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
{
CALayer *theLayer = 0;
if(anim==[_b1 animationForKey:#"opacity"])
theLayer = _b1; // I have two layers
else
if(anim==[_b2 animationForKey:#"opacity"])
theLayer = _b2;
if(theLayer)
{
CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
[theLayer setOpacity:toValue];
[theLayer removeAllAnimations];
}
}
}
The easiest solution is to use implicit animations. This will handle all of that trouble for you:
self.layer?.backgroundColor = NSColor.red.cgColor;
If you want to customize e.g. the duration, you can use NSAnimationContext:
NSAnimationContext.beginGrouping();
NSAnimationContext.current.duration = 0.5;
self.layer?.backgroundColor = NSColor.red.cgColor;
NSAnimationContext.endGrouping();
Note: This is only tested on macOS.
I initially did not see any animation when doing this. The problem is that the layer of a view-backed layer does not implicit animate. To solve this, make sure you add a layer yourself (before setting the view to layer-backed).
An example how to do this would be:
override func awakeFromNib() {
self.layer = CALayer();
//self.wantsLayer = true;
}
Using self.wantsLayer did not make any difference in my testing, but it could have some side effects that I do not know of.
It seems that removedOnCompletion flag set to false and fillMode set to kCAFillModeForwards doesn't work for me either.
After I apply new animation on a layer, an animating object resets to its initial state and then animates from that state.
What has to be done additionally is to set the model layer's desired property according to its presentation layer's property before setting new animation like so:
someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:#"someAnimation"];
Here is a sample from playground:
import PlaygroundSupport
import UIKit
let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red
//--------------------------------------------------------------------------------
let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7
view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9
//--------------------------------------------------------------------------------
PlaygroundPage.current.liveView = view
Create an animation model
Set start position of the animation (could be skipped, it depends on your current layer layout)
Set end position of the animation
Set animation duration
Delay animation for a second
Do not set false to isRemovedOnCompletion - let Core Animation clean after the animation is finished
Here is the first part of the trick - you say to Core Animation to place your animation to the start position (you set in step #2) before the animation has even been started - extend it backwards in time
Copy prepared animation object, add it to the layer and start the animation after the delay (you set in step #5)
The second part is to set correct end position of the layer - after the animation is deleted your layer will be shown at the correct place

Resources