Flipping x animation with repetition after a certain duration - ios

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()
}
}

Related

Animate shadow properties

I want to animate shadow properties for a view. Initially I tried:
func animateIn(delay: TimeInterval = 0) {
UIView.animate(withDuration: 0.2, delay: delay, options: .allowUserInteraction, animations: {
self.shadowView.layer.shadowColor = UIColor.black.cgColor
self.shadowView.layer.shadowOffset = CGSize(width: 15, height: 15)
self.shadowView.layer.shadowRadius = 20
self.shadowView.layer.shadowOpacity = 0.2
self.layoutIfNeeded()
}, completion: nil)
}
which doesnt work given that UIView.animate doesnt work with layers.
I tested the following post (How to animate layer shadowOpacity?), but it only specifies how to animate the shadow Opacity. Is there any way to animate all my shadow properties like I would with an animation blocl?
You are very close, you just create multiple CABasicAnimations for all the property changes and then you can use CAAnimationGroup
So let say you have 2 CABasicAnimation animations like:
let shadownOpacityAnimation = CABasicAnimation(keyPath: "shadowOpacity")
...
let shadowRadiusAnimation = CABasicAnimation(keyPath: "shadowRadius")
...
Then you can add them using CAAnimationGroup
let group = CAAnimationGroup()
group.duration = 0.2
group.repeatCount = 1
group.autoreverses = false
group.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
group.animations = [shadownOpacityAnimation, shadowRadiusAnimation]
layer.add(group, forKey: "allAnimations")
You can read more about it in this answer:
How can I create an CABasicAnimation for multiple properties?

Swift: consecutive CABasicAnimations that repeat

Goal:
First animation runs for a duration of 1.0
At 1.0, the second animation runs (while the first animation autoreverses back to its starting point)
At 2.0, the first animation is back at its starting point, the second animation has completed, and the first animation repeats
At 3.0, the second animation runs as the first animation concludes its second run
My code:
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.fromValue = 1.0
animation.toValue = 1.5
animation.duration = 1.0
animation.autoreverses = true
animation.repeatCount = .greatestFiniteMagnitude
image1.layer.add(animation, forKey: animation.keyPath)
let animation2 = CABasicAnimation(keyPath: "transform.scale")
animation2.fromValue = 1.0
animation2.toValue = 2.0
animation2.duration = 1.0
animation2.fillMode = .forwards
let animation2b = CABasicAnimation(keyPath: "opacity")
animation2b.fromValue = 1.0
animation2b.toValue = 0.0
animation2b.duration = 1.0
animation2b.fillMode = .forwards
let animationGroup = CAAnimationGroup()
animationGroup.animations = [animation2, animation2b]
animationGroup.duration = 2.0
animationGroup.beginTime = 1.0
animationGroup.repeatCount = .greatestFiniteMagnitude
image2.layer.add(animationGroup, forKey: "scaleAndFade")
The goal is to start the 2nd animation 1.0 after the 1st animation. And since the animation group has a duration of 2.0 while the animations within it have a duration of only 1.0, the animation would start at 1.0, end at 2.0, and then not repeat again until 3.0
The two animations sometimes match up, but not on every build. Is there a more surefire way of starting the second animation to begin exactly at the end of the initial animation's first completed animation? So that they'll be in sync from that point on. Thanks for any help!
I’m a little unclear on what we’re trying to achieve, but my sense is that the idea is that we have two views with repeating animations that need to be coordinated. To demonstrate an example of how this might be done, I chose to make both animations be a scale-up followed by a scale-down:
The animated gif here comes to an end after a few repetitions, but in actual fact the repetition just goes on forever.
That was simply achieved as follows:
func step1() {
UIView.animate(withDuration: 1, animations: {
self.v1.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
self.v2.transform = .identity
}) { _ in
DispatchQueue.main.async {
self.step2()
}
}
}
func step2() {
UIView.animate(withDuration: 1, animations: {
self.v1.transform = .identity
self.v2.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
DispatchQueue.main.async {
self.step1()
}
}
}
The point is that the stages of the animation can never get out of sync between the two views, because each stage involves both views and follows the previous stage. So I’m pretty sure you can adapt that approach to fit your animation.
I like #matt's answer and always appreciate their input but since I was trying to use CAAnimation (specifically I wanted to use CAKeyframeAnimation), I ended up nesting two CATransactions using CATransaction.begin() and CATransaction.setCompletionBlock to start one animation exactly at the end of the other and then to recursively call the function repeatedly.
CATransaction documentation

What is a correct way to layer styling before begin time of core animation?

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.

Spinning objects in iOS

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);

UIView.animateWithDuration: animation always stops at original position

I added an ImageView in the middle of my view (no constraints set).
With this code I want to move this ImageView from its original position to a new one.
Unfortunately, it moves from the new to its original position - why?
let opts = UIViewAnimationOptions.CurveEaseInOut
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
self.ivSun.center.x += 100
}, completion: nil)
Add this after completion of your animation.
self.ivSun.layer.position = self.ivSun.layer.presentationLayer().position
I have code that implemented in my game.
CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
self.viewBall.layer.position = self.viewBall.layer.presentationLayer().position
}
}
var animation = CABasicAnimation(keyPath: "position")
animation.duration = ballMoveTime
animation.fromValue = NSValue(CGPoint: viewBall.layer.presentationLayer().position)
animation.toValue = NSValue(CGPoint: CGPointMake(self.borderRight, self.borderMiddle))
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
viewBall.layer.addAnimation(animation, forKey: "transform")
CATransaction.commit()
Change position value as you need.
Try this one.
func animateImage()
{
UIView.animateWithDuration(3.0, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.imageView.center = CGPointMake(self.imageView.center.x+10, self.imageView.center.y+10)
}, completion: nil)
}
Without knowing more about the context and related code it's difficult to give an answer on "why" it's snapping back to its original position, but as an alternative you could set the completion handler with whatever you'd like as the final position/coordinate.
Generally I use constraints on my widgets/views, and I animate their values (versus animating the widgets' frame, for example) ... with excellent results.
This is due to autolayout. Constraints need to be changed and updated in the the animation as seen below:
- (IBAction)moveAction:(id)sender {
self.spaceConstraint.constant += 220;
[UIView animateWithDuration:0.5 animations:^{
[self.imageView layoutIfNeeded];
}];
}
var animation = CABasicAnimation(keyPath: "position")
animation.duration = ballMoveTime
animation.fromValue = NSValue(CGPoint: viewBall.layer.presentationLayer().position)
animation.toValue = NSValue(CGPoint: CGPointMake(self.borderRight, self.borderMiddle))
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
viewBall.layer.addAnimation(animation, forKey: "transform")
CATransaction.commit()

Resources