UIView.animateKeyFramesWithDuration not animating smoothly - swift

I am trying to write a blinking animation, but the timing of it feels off. I made a simple version in a playground:
import UIKit
import XCPlayground
let v = UIView()
v.frame.size = CGSize(width: 200, height: 200)
v.backgroundColor = UIColor.redColor()
v.layer.cornerRadius = 100
UIView.animateKeyframesWithDuration(1, delay: 0, options: .Repeat, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0.5, animations: {
v.alpha = 0.0
})
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5, animations: {
v.alpha = 0.5
})
}, completion: nil)
XCPlaygroundPage.currentPage.liveView = v
The view fades from 0 to 0.5 and then appears to stutter at full alpha for a second before resuming the animation. I noticed the same thing in the simulator.
Is there something I am missing about how the keyframes are supposed to work?

Upon inspection, I believe the default for your v view's alpha is 1.0. This means that after your animation ends for a split second it is at full alpha again, and then the animation is repeated. To compensate for this and acquire the effect you want, you may consider setting its alpha to 0.0 before you run XCPlaygroundPage.currentPage.liveView = v.
Updates:
You can break the animation your code into 4 states.
At the start, the alpha is 1.0.
The first keyframe changes the alpha to 0.0 in 0.5 seconds.
The second keyframe changes the alpha to 0.5 in 0.5 seconds.
At this point, the animation has ended. So the v view reverts to state 1 and repeats then animation.
State 4 is where the blink of full alpha occurs because the v view is going from 0.5 to 1.0 in 0.0 seconds. However, the computer cannot make anything happen in 0.0 seconds (not actually possible because of complicated physics) so the result is a split second flash of full alpha as the computer tries to get as close to 0.0 seconds as it can.
To circumvent this you can either set the original alpha to 0.5 so that way the animation's state 1 will be the same as the result of its state 3, or you can add another keyframe that brings the alpha back to 0.0 before the animation is over:
Examples:
Option 1:
//Import Statements
//Configuration and Initialization of v view
v.alpha = 0.5 //<--- This is the key point
UIView.animateKeyframesWithDuration(1, delay: 0, options: .Repeat, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0.5, animations: {
v.alpha = 0.0
})
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5, animations: {
v.alpha = 0.5
})
}, completion: nil)
XCPlaygroundPage.currentPage.liveView = v
Option 2:
//Import Statements
//Configuration and Initialization of v view
v.alpha = 0.0 //<-- Make sure to set the original alpha to 0.0
let duration: NSTimeInterval = 1.8
let third: NSTimeInterval = 1/3
UIView.animateKeyframesWithDuration(duration, delay: 0, options: .Repeat, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: third, animations: {
v.alpha = 0.0
})
UIView.addKeyframeWithRelativeStartTime(third, relativeDuration: third, animations: {
v.alpha = 0.5
})
//Key Point Below. Added Another Frame!
UIView.addKeyframeWithRelativeStartTime(third*2, relativeDuration: third, animations: {
v.alpha = 0.0
})
}, completion: nil)
XCPlaygroundPage.currentPage.liveView = v

Related

How to create twitter tab bar push animation

I have the following code:
private var bounceAnimation: CAKeyframeAnimation = {
let bounceAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
bounceAnimation.values = [1.0, 1.4, 0.9, 1.02, 1.0]
bounceAnimation.duration = TimeInterval(0.3)
bounceAnimation.calculationMode = CAAnimationCalculationMode.cubic
return bounceAnimation
}()
This creates the animation where the icon gets bigger and then smaller. I am trying to create the animation where the icon gets smaller and then back to normal like it's being pushed similar to twitter, Spotify, etc. I assume it's just changing around the bounce values all though I'm not sure how would I do this.
I'd use a normal UIView.animate function like this:
UIView.animate(withDuration: 0.05, delay: 0, options: .curveLinear, animations: {
view.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
}, completion: nil)
UIView.animate(withDuration: 0.3, delay: 0.05, usingSpringWithDamping: 0.2, initialSpringVelocity: 7, options: .curveEaseOut, animations: {
view.transform = .identity
}, completion: nil)
Just change view to be whatever view you're trying to animate. Then mess around with the initial scale, the duration and the spring dampening to get the animation you want!

UIView animations with scale and alpha

I need make animation to show/hide view (UIButton), so alpha changes from 0 to 1 same time as scale of view changes from 0 to 100% (showing) and reverse (hiding). Each animation I can do separately but how make it together? And It must showing/hiding correct when user randomly (and may be so fast, for example, hiding animation not finished - showing animation starts) interact. Any help will be appreciated.
continuing the previous comments, I try to make a simple example.
so maybe the trick is in the transform "identity" which functions to restore the view to its origin, hopefully it helps
#IBAction func showButtonTapped(_ sender: Any) {
UIView.animate(withDuration: 1.0, delay: 0.5, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: [.curveEaseIn], animations: {
self.yourView.transform = .identity
self.yourView.alpha = 1
}, completion: nil)
}
#IBAction func hideButtonTapped(_ sender: Any) {
UIView.animate(withDuration: 1.0, delay: 0.5, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: [.curveEaseOut], animations: {
self.yourView.transform = CGAffineTransform.identity.scaledBy(x: 0.7, y: 0.7)
self.yourView.alpha = 0
})
}

Immediate UIButton rotation transform, with animated scale

I would like to animate the scale of a UIButton, and animate the scale again when completed, but have it rotate without animation. I've tried to put the rotation transform in an animation call with no duration, but somehow it still makes it part of or replace the scaling animation.
I extended the animation of the scale by a second to demonstrate the result more clearly.
let transforms: CGAffineTransform = .identity
button.transform = transforms
UIView.animate(withDuration: 1.05, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = transforms.scaledBy(x: 0.75, y: 0.75)
}, completion: { _ in
button.transform = CGAffineTransform(rotationAngle: 90 * (.pi/180)) // I want this to happen immediately, without animation
UIView.animate(withDuration: 1.1, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = transforms.scaledBy(x: 1.0, y: 1.0)
}, completion:nil)
})
You set the rotate transform to happen immediately - and it does! But then you immediately start animating it again, with respect to your transforms variable - reverting its current transform to the one in that variable.
Instead of using a variable to keep track of the button's transform, just refer to its current transform, like so:
button.transform = .identity
UIView.animate(withDuration: 1.05, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = button.transform.scaledBy(x: 0.75, y: 0.75)
}, completion: { _ in
button.transform = CGAffineTransform(rotationAngle: 90 * (.pi/180)) // I want this to happen immediately, without animation
UIView.animate(withDuration: 1.1, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = button.transform.scaledBy(x: 1.0, y: 1.0)
}, completion:nil)
})

Resize image to its initial size

I am using 2 animations.
when the screen is launched, the first animation starts in viewDidLoad.
this animation is applied to 2 images called layer2 and layer3.
func firstAnimation(){
UIView.animate(withDuration: 1, delay: 0, options:
UIViewAnimationOptions.repeat , animations: {
self.layer2.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
self.layer3.transform = CGAffineTransform(scaleX: 2, y: 2)
}, completion: { finished in
})
}
when an image called layer0 is long pressed, then firstAnimation() is being stopped by this code:
layer2.layer.removeAllAnimations()
layer3.layer.removeAllAnimations()
and different animation is being applied to layer2 and layer3.
the second animation is
func secondAnimation() {
UIView.animate(withDuration: 20, delay: 0, options:
UIViewAnimationOptions.curveEaseOut , animations: {
self.layer2.transform = CGAffineTransform(scaleX: 10, y: 10)
self.layer3.transform = CGAffineTransform(scaleX: 10, y: 10)
}, completion: { finished in
})
}
when I want to delete the second animation and launch the first one again, the 2 images layer2 and layer3 are starting from the size that they gained because of the second animation. how to relaunch the first animation with the initial sizes of the images?
You can reset views that were transformed to their original scale by using CGAffineTransform.identity.
For example:
layer2.transform = .identity

UIViewAnimationOptions.CurveEaseIn not working inside coordinated animation

I am in the middle of developing a tvOS application.
In my application I have a UICollectionView. On focus I am using a coordinated animation. Inside that animation I have this code:
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.bg.image = finalImage
self.bg.alpha = 1.0
}, completion: nil)
But it seems like its not using the curveEaseIn animation option.
Its changing the alpha bluntly, which is odd. Where I am going wrong?
Sorry. Forgot to post the answer.
I did animating the imageview in the completion of the coordinated animation like this
self.bg.alpha = 0.0
coordinator.addCoordinatedAnimations({ //code for coordinated animation}, completion: { _ in
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.bg.image = self.finalImage
self.bg.alpha = 1.0
}, completion: nil)})
What is the desired effect? Are you trying to cross fade bg.image with a new image? Have you tried changing the duration to something higher, for example 10.0, to see if the animation is perhaps happening too fast?
Here's an example of what you may be looking for:
let myImageView = UIImageView()
let myFinalImage = UIImage(named:"FinalImage.png")
// Animate alpha
UIView.animateWithDuration(1.0, delay: 0.0, options: .CurveEaseIn, animations: {
print("alpha animation started")
myImageView.alpha = 0.0
}) { (finished) in
print("alpha animation finished")
}
// Animate change of image
UIView.transitionWithView(myImageView, duration: 1.0, options: .TransitionCrossDissolve, animations: {
print("cross dissolve animation started")
myImageView.image = myFinalImage
}) { (finished) in
print("cross dissolve animation finished")
}
In Swift 4 or Swift 5 use: .curveEaseIn
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseIn, animations: {
...
}) { (finished) in
...
}