Programming an animation to loop forever in Swift - ios

I'm using a code snippit from the Stack Overflow post: How to make "shaking" animation
I pasted this code into my project and it works perfectly:
func shakeView(){
var shake:CABasicAnimation = CABasicAnimation(keyPath: "position")
shake.duration = 0.1
shake.repeatCount = 2
shake.autoreverses = true
var from_point:CGPoint = CGPointMake(LoginField.center.x - 5, LoginField.center.y)
var from_value:NSValue = NSValue(CGPoint: from_point)
var to_point:CGPoint = CGPointMake(LoginField.center.x + 5, LoginField.center.y)
var to_value:NSValue = NSValue(CGPoint: to_point)
shake.fromValue = from_value
shake.toValue = to_value
LoginField.layer.addAnimation(shake, forKey: "position")
}
I however want this animation to loop until another function is called. How do I make it loop indefinite and how can I make it stop when another function is called?

First, set the repeatCount to HUGE_VALF / greatestFiniteMagnitude:
Setting this property to HUGE_VALF will cause the animation to repeat forever.
Second, call LoginField.layer.removeAllAnimations() or LoginField.layer.removeAnimationForKey("position")when you want to stop the animation.
Edit: Apparently Swift does not know HUGE_VALF, setting the repeatCount to Float.infinity should work.

Use shake.repeatCount = Float.infinity

Related

"zPosition" CABasicAnimation not working

I used an animation along the z axis a long time ago in Objective C :
CABasicAnimation *positionAnimation;
positionAnimation=[CABasicAnimation animationWithKeyPath:#"zPosition"];
positionAnimation.fromValue=[NSNumber numberWithFloat:frandom(800, 400)];
positionAnimation.toValue=[NSNumber numberWithFloat:frandom(-300, -100)];
positionAnimation.duration=duration;
positionAnimation.repeatCount = HUGE_VALF;
positionAnimation.removedOnCompletion = NO;
But it is not working anymore on swift 3, the layer does not move at all.
let positionAnimation = CABasicAnimation(keyPath: "zPosition")
positionAnimation.fromValue = NSNumber(value: Float.random(from: 800, 400))
positionAnimation.toValue = NSNumber(value: Float.random(from: -300, -100))
positionAnimation.duration = duration
positionAnimation.repeatCount = 400
positionAnimation.isRemovedOnCompletion = false
Did i miss something ?
thank you.

iOS - Facebook pop framework - Repeat forever "shake" animation

I'm using Facebook pop framework to perform some cool animations. I'm shaking a button in this way :
let rotation = POPSpringAnimation.init(propertyNamed: kPOPLayerRotation)
rotation.springBounciness = 30
rotation.springSpeed = 20
rotation.velocity = 30.0
rotation.repeatForever = true
button.layer.pop_addAnimation(rotation, forKey: "rotation")
Despite of the repeatForever set to true the animation doesn't repeat. I noticed that if we have the toValue property set, the animation repeats. Am I doing something wrong?
I solved this issue adding the following:
rotation.fromValue = 0.0
You can do it with POPBasicAnimation. If you're rotating forever, you may not need the spring animation.
Looking at your code, you don't have a rotation.toValue You need to tell the animation how far to rotate. Try this:
func configureBtnRotation(btn: UIButton) {
let rotation = POPBasicAnimation(propertyNamed: kPOPLayerRotation)
rotation.toValue = 90.0
rotation.duration = 100.0 //this sets the speed of rotation
rotation.repeatForever = true
button.layer.pop_addAnimation(rotation, forKey: "rotation")
}
Hope this helps.

How to animate UITextField while reducing width dynamically in Swift?

I am creating an application in which I am showing two textfields at login page. In that if user enters incorrect input then it will show Cross symbol in front of the textfields. For showing this symbol I am reducing width of UITextField. I want to do some animation while reducing the width. I have done the following :
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.1
animation.repeatCount = 1
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(textFieldEmail.center.x - 5, textFieldEmail.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(textFieldEmail.center.x + 5, textFieldEmail.center.y))
textFieldEmail.layer.addAnimation(animation, forKey: "position")
self.imageViewCrossPassword.hidden = false
self.passwordRightMarginContraint.constant = 25
The above code is applying shake animation for the same. But I want something different which I can apply to only right side of UITextField. Suggestions are always appreciated. :-)
You can add a width constraint on textFieldEmail and animate it's change with the code like this
self.emailWidthConstraint.constant = self.textFieldEmail.bounds.size.width + 5;
UIView.animateWithDuration(0.1, delay: 0, options:.Autoreverse, animations: { () -> Void in
self.textFieldEmail.layoutIfNeeded()
}, completion: nil);

Keeping a background image in the center on animation

I have animation setup to resize the image to about 1.3 times it's original size. The animations and everything are working without a problem but the image is moving towards the top left. Which means that the position of the image is not centering upon resize. How do i solve this problem
These are the animations I setup
var borderWidth:CABasicAnimation = CABasicAnimation(keyPath: "borderWidth")
borderWidth.fromValue = 0
borderWidth.toValue = 5
borderWidth.repeatCount = Float.infinity
sender.layer.borderWidth = 0
var increaseButtonHeight:CABasicAnimation = CABasicAnimation(keyPath: "bounds.size.height")
increaseButtonHeight.fromValue = sender.frame.size.height
increaseButtonHeight.toValue = sender.frame.size.height * 1.3
var increaseButtonWidth: CABasicAnimation = CABasicAnimation(keyPath: "bounds.size.width")
increaseButtonWidth.fromValue = sender.frame.size.width
increaseButtonWidth.toValue = sender.frame.size.width * 1.3
var boom:CAAnimationGroup = CAAnimationGroup()
boom.animations = [borderWidth,increaseButtonWidth, increaseButtonHeight]
boom.repeatCount = Float.infinity
boom.duration = 0.5
boom.autoreverses = true
sender.layer.addAnimation(boom, forKey: "boom")
Do I need to setup a new animation for centering the button continuously as the animation happens?
Please help
Nikhil
Set the property contentsGravity of the layer to kCAGravityCenter

CATransaction: a view flashes on completion

I'm writing a little bit complex animation, which goes in 2 steps:
Change opacity to 0 of UIViews that are not need to be visible and move a UIImageView (which has alpha = 1) to another CGPoint (position).
Change opacity of another UIView to 1 and the opacity of the UIImageView from the previous step to 0, and then after the animation of this step is finished, remove UIImageView from superview.
I've done it this way:
The first step is done without an explicit CATransaction. These 2 animations just have beginTime set to CACurrentMediaTime(). And I'm applying changes to the views right after layer.addAnimation(...) call.
Everything works fine here.
In the second step implementation I call CATransaction.begin() at the beginning.
Inside begin/commit calls to CATransaction I create and add 2 CABasicAnimations to 2 different layers: one for changing the opacity from 0 to 1 (for UIView), and one for changing the opacity from 1 to 0 (for UIImageView). Both animations have beginTime set to CACurrentMediaTime() + durationOfThePreviousStep.
And right after CATransaction.begin() I call CATransaction.setCompletionBlock({...}), and in this completion block I apply changes to these two views: set their new alphas and remove UIImageView from superview.
The problem is, at the end of this whole animation the UIView that has alpha animated to 1 flashes, which means its alpha sets back to 0 (though I've set its alpha to 1 in the completion block) and right after this the completion block executes and its alpha goes up to 1 again.
Well, the question is, how to get rid of this flashing? Maybe this animation can be done in better way?
P.S. I'm not using UIView animations because I'm interested in custom timing functions for these animations.
EDIT 1:
Here's the code. I've deleted UIImageView alpha animation because it's not really necessary.
var totalDuration: CFTimeInterval = 0.0
// Alpha animations.
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 1
alphaAnimation.toValue = 0
alphaAnimation.beginTime = CACurrentMediaTime()
alphaAnimation.duration = 0.15
let alphaAnimationName = "viewsFadeOut"
view1.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view1.alpha = 0
view2.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view2.alpha = 0
view3.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view3.alpha = 0
view4.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view4.alpha = 0
// Image View moving animation.
// Add to total duration.
let rect = /* getting rect */
let newImagePosition = view.convertPoint(CGPoint(x: CGRectGetMidX(rect), y: CGRectGetMidY(rect)), fromView: timeView)
let imageAnimation = CABasicAnimation()
imageAnimation.keyPath = "position"
imageAnimation.fromValue = NSValue(CGPoint: imageView!.layer.position)
imageAnimation.toValue = NSValue(CGPoint: newImagePosition)
imageAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
imageAnimation.beginTime = CACurrentMediaTime()
imageAnimation.duration = 0.3
imageView!.layer.addAnimation(imageAnimation, forKey: "moveImage")
imageView!.center = newImagePosition
totalDuration += imageAnimation.duration
// Time View alpha.
CATransaction.begin()
CATransaction.setCompletionBlock {
self.timeView.alpha = 1
self.imageView!.removeFromSuperview()
self.imageView = nil
}
let beginTime = CACurrentMediaTime() + totalDuration
let duration = 0.3
alphaAnimation.fromValue = 0
alphaAnimation.toValue = 1
alphaAnimation.beginTime = beginTime
alphaAnimation.duration = duration
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
/* imageView alpha animation is not necessary, so I removed it */
CATransaction.commit()
EDIT 2: Piece of code that cause the problem:
CATransaction.begin()
CATransaction.setCompletionBlock {
self.timeView.alpha = 1
}
let duration = 0.3
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = duration
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
CATransaction.commit()
Maybe the problem is because the timeView has a UITextField and a UIScrollView with 4 subviews. I've tried to replace timeView with a snapshot of timeView (UIImageView), but that didn't help.
EDIT 3:
New code. With this code, timeView always has alpha = 1, and it also animates from 0 to 1.
CATransaction.begin()
CATransaction.setCompletionBlock {
self.imageView!.removeFromSuperview()
self.imageView = nil
}
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = 0.3
alphaAnimation.beginTime = beginTime
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
timeView.alpha = 1.0
CATransaction.commit()
Just looking at your code, I would expect it to flash. Why? Because you have animated timeView's layer's opacity from 0 to 1, but you have not set it to 1 (except in the completion handler, which will happen later). Thus, we animate the presentation layer from 0 to 1 and then the animation ends and it is revealed that the opacity of the real layer was 0 all along.
So, set timeView's layer's opacity to 1 before your animation gets going. Also, since you are using a delayed beginTime, you will need to set your animation's fillMode to "backwards".
I was able to get good results by modifying your test code to be self-contained and to look like this; there is a delay, the view fades in, and there is no flash at the end:
CATransaction.begin()
let beginTime = CACurrentMediaTime() + 1.0 // arbitrary, just testing
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = 1.0 // arbitrary, just testing
alphaAnimation.fillMode = "backwards"
alphaAnimation.beginTime = beginTime
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
timeView.layer.opacity = 1.0
CATransaction.commit()
There are some other things about your code that I find rather odd. It is somewhat risky using a transaction completion block in this way; I don't see why you don't give your animation a delegate. Also, you are reusing alphaAnimation multiple times; I can't recommend that. I would create a new CABasicAnimation for each animation, if I were you.

Resources