I'm working on a Swift animation and I'm currently trying to refactor the animation code since my I'm trying to use the CAkeyframeAnimation but now I struggle with how to convert UIView animate method to CAKeyframeAnimation.
This is the one using UIView animate function.
class myCustomView: UIView {
func showPointOfInterest(at point: CGPoint, hideViewAfterAnimation: Bool) {
if isPreviousAnimationPresenting() { layer.removeAllAnimations() }
center = point
transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
self.alpha = 1
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut]) {
self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
} completion: { animated in
if !animated {
return
} else {
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut]) {
self.transform = .identity
} completion: { animated in
if !animated {
return
} else {
UIView.animate(withDuration: 1.5, delay: 0, options: [.curveEaseInOut]) {
self.alpha = 0.5
} completion: { animated in
if !animated {
return
} else {
UIView.animate(withDuration: 0.5, delay: 0, options: [.curveEaseInOut]) {
self.alpha = hideViewAfterAnimation ? 0 : 0.5
}
}
}
}
}
}
}
}
Generally, these are the things I struggled with...
I'm not sure what is the keyPath is gonna be.
In my UIView animate function, I change the transform and alpha value. I saw examples put string for the keyPath, like CAKeyframeAnimation(keyPath: "position.x"), so what should I put for my keyPath?
Since I want to change the size of the view by using CGAffineTransform in the UIView animate function, do I put CGAffineTransform(scaleX:, y: ) in the transformKeyframeAnimation.values?
func showPointOfInterest(at point: CGPoint, hideViewAfterAnimation: Bool) {
if isPreviousAnimationPresenting() { layer.removeAllAnimations() }
center = point
transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
alpha = 1
let transformKeyframeAnimation = CAKeyframeAnimation(keyPath: "transform")
transformKeyframeAnimation.values = [CGAffineTransform(scaleX: 0.9, y: 0.9), CGAffineTransform(scaleX: 1, y: 1)]
transformKeyframeAnimation.duration = 1.0
transformKeyframeAnimation.isRemovedOnCompletion = hideViewAfterAnimation
self.layer.add(transformKeyframeAnimation, forKey: "transform")
}
I want the arranged subviews of a stack view to become visible in a cascaded manner, but they're all appearing at the same time:
let i = 5
let animation = UIViewPropertyAnimator(duration: 5, timingParameters: UICubicTimingParameters())
animation.addAnimations {
UIView.animateKeyframes(withDuration: 0, delay: 0, options: .calculationModeCubicPaced) {
for index in 0..<i {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: Double(1/i)) {
let label = self.stackView.arrangedSubviews[index]
label.alpha = 1
}
}
} completion: { (_) in
return
}
}
animation.startAnimation()
My expectation is that setting the options to calculationModePaced or calculationModeCubicPaced animates each key frame in an evenly spaced-out manner even if I don't set the withRelativeStartTime parameter individually (and have them at 0).
I've tried changing the option to calculationModeLinear and manually setting the withRelativeStartTime:
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
let label = self.stackView.arrangedSubviews[0]
label.alpha = 1
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
let label = self.stackView.arrangedSubviews[1]
label.alpha = 1
}
but, the labels still show up at the same time.
To make it work as you expected you should:
remove , options: .calculationModeCubicPaced
fix relativeDuration - 1.0 / Double(i)
setup withRelativeStartTime - Double(index) / Double(i)
example:
let totalCount = labels.count
let labelDuration = 1.0 / Double(totalCount)
let animation = UIViewPropertyAnimator(duration: 5, timingParameters: UICubicTimingParameters())
animation.addAnimations {
UIView.animateKeyframes(withDuration: 0, delay: 0, animations: { [weak self] in
for i in 0..<totalCount {
UIView.addKeyframe(withRelativeStartTime: Double(i) / Double(totalCount), relativeDuration: labelDuration) {
self?.labels[i].alpha = 1
}
}
})
}
animation.startAnimation()
I am using this code to rotate a view 360 degrees infinitely. But my view is not rotating:
func rotateImageView() {
UIView.animate(withDuration: 3.0, delay: 0, options: [.repeat, .curveLinear], animations: {
self.vinylView.transform = CGAffineTransform(rotationAngle: .pi * 2)
})
}
How to fix it?
Substitute pi * with /, delete repeat, add completion and recall the function like this:
private func rotateImageView() {
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
self.vinylView.transform = self.vinylView.transform.rotated(by: .pi / 2)
}) { (finished) in
if finished {
self.rotateImageView()
}
}
}
Solution using CABasicAnimation
// Rotate vinvlView
vinylView.layer.add(CABasicAnimation.rotation, forKey: nil)
extension CABasicAnimation {
static let rotation : CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.repeatCount = .infinity // Rotate a view 360 degrees infinitely
animation.fromValue = 0
animation.toValue = CGFloat.pi * 2
animation.duration = 3.0
return animation
}()
}
I have an animate function which contains CATransaction.begin()
I want this animation to be repeated infinitely or for a defined number of times.
How do I make that happen?
This is the animate function if you need to see the code:
private func animate(views: [UIView], duration: TimeInterval, intervalDelay: TimeInterval) {
CATransaction.begin()
CATransaction.setCompletionBlock {
print("COMPLETED ALL ANIMATIONS")
}
var delay: TimeInterval = 0.0
let interval = duration / TimeInterval(views.count)
for view in views {
let transform = view.transform
UIView.animate(withDuration: interval, delay: delay, options: [.curveEaseIn], animations: {
view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}, completion: { (finished) in
UIView.animate(withDuration: interval, delay: 0.0, options: [.curveEaseIn], animations: {
view.transform = transform
}, completion: { (finished) in
})
})
delay += (interval * 2.0) + intervalDelay
}
CATransaction.commit()
}
I think CATransaction redundant there
If I understand what you want to achieve
UIView.animate(withDuration: interval, delay: delay, options: [.curveEaseIn, .autoreverse, .repeat], animations: {
self.views.forEach{$0.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)}
}, completion: nil)
EDIT: Recursive function to do pulse in circles
func pulse(index: Int) {
guard views.count > 0 else { return }
let resolvedIndex = (views.count < index) ? index : 0
let duration = 1.0
let view = views[resolvedIndex]
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseIn,.autoreverse], animations: {
self.view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}) { [weak self] _ in
self?.pulse(index: resolvedIndex + 1)
}
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I'm trying to do scale animation for UIButton when its clicked but what I'm trying to accomplish is when the button clicked I need the UIButton to be smaller to the inside then it comes back to its same size (like a bubble).
I tried the following:
button.transform = CGAffineTransformMakeScale(-1, 1)
UIView.animateWithDuration(0.5, animations: { () -> Void in
button.transform = CGAffineTransformMakeScale(1,1)
})
Try this
UIView.animate(withDuration: 0.6,
animations: {
self.button.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
},
completion: { _ in
UIView.animate(withDuration: 0.6) {
self.button.transform = CGAffineTransform.identity
}
})
SWIFT 5 Code Update :I have animated button with a nice bouncing effect , with spring animation.
#IBOutlet weak var button: UIButton!
#IBAction func animateButton(sender: UIButton) {
sender.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
UIView.animate(withDuration: 2.0,
delay: 0,
usingSpringWithDamping: CGFloat(0.20),
initialSpringVelocity: CGFloat(6.0),
options: UIView.AnimationOptions.allowUserInteraction,
animations: {
sender.transform = CGAffineTransform.identity
},
completion: { Void in() }
)
}
All of the answers above are valid.
As a plus, with Swift I suggest to create an extension of UIView in order to "scale" any view you want.
You can take inspiration from this piece of code:
SWIFT 5.0
extension UIView {
/**
Simply zooming in of a view: set view scale to 0 and zoom to Identity on 'duration' time interval.
- parameter duration: animation duration
*/
func zoomIn(duration: TimeInterval = 0.2) {
self.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveLinear], animations: { () -> Void in
self.transform = .identity
}) { (animationCompleted: Bool) -> Void in
}
}
/**
Simply zooming out of a view: set view scale to Identity and zoom out to 0 on 'duration' time interval.
- parameter duration: animation duration
*/
func zoomOut(duration : TimeInterval = 0.2) {
self.transform = .identity
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveLinear], animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
}) { (animationCompleted: Bool) -> Void in
}
}
/**
Zoom in any view with specified offset magnification.
- parameter duration: animation duration.
- parameter easingOffset: easing offset.
*/
func zoomInWithEasing(duration: TimeInterval = 0.2, easingOffset: CGFloat = 0.2) {
let easeScale = 1.0 + easingOffset
let easingDuration = TimeInterval(easingOffset) * duration / TimeInterval(easeScale)
let scalingDuration = duration - easingDuration
UIView.animate(withDuration: scalingDuration, delay: 0.0, options: .curveEaseIn, animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: easeScale, y: easeScale)
}, completion: { (completed: Bool) -> Void in
UIView.animate(withDuration: easingDuration, delay: 0.0, options: .curveEaseOut, animations: { () -> Void in
self.transform = .identity
}, completion: { (completed: Bool) -> Void in
})
})
}
/**
Zoom out any view with specified offset magnification.
- parameter duration: animation duration.
- parameter easingOffset: easing offset.
*/
func zoomOutWithEasing(duration: TimeInterval = 0.2, easingOffset: CGFloat = 0.2) {
let easeScale = 1.0 + easingOffset
let easingDuration = TimeInterval(easingOffset) * duration / TimeInterval(easeScale)
let scalingDuration = duration - easingDuration
UIView.animate(withDuration: easingDuration, delay: 0.0, options: .curveEaseOut, animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: easeScale, y: easeScale)
}, completion: { (completed: Bool) -> Void in
UIView.animate(withDuration: scalingDuration, delay: 0.0, options: .curveEaseOut, animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
}, completion: { (completed: Bool) -> Void in
})
})
}
}
Usage is very simply:
let button = UIButton(frame: frame)
button.zoomIn() // here the magic
Swift 3 Version
extension UIView {
/**
Simply zooming in of a view: set view scale to 0 and zoom to Identity on 'duration' time interval.
- parameter duration: animation duration
*/
func zoomIn(duration: TimeInterval = 0.2) {
self.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveLinear], animations: { () -> Void in
self.transform = CGAffineTransform.identity
}) { (animationCompleted: Bool) -> Void in
}
}
/**
Simply zooming out of a view: set view scale to Identity and zoom out to 0 on 'duration' time interval.
- parameter duration: animation duration
*/
func zoomOut(duration: TimeInterval = 0.2) {
self.transform = CGAffineTransform.identity
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveLinear], animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
}) { (animationCompleted: Bool) -> Void in
}
}
/**
Zoom in any view with specified offset magnification.
- parameter duration: animation duration.
- parameter easingOffset: easing offset.
*/
func zoomInWithEasing(duration: TimeInterval = 0.2, easingOffset: CGFloat = 0.2) {
let easeScale = 1.0 + easingOffset
let easingDuration = TimeInterval(easingOffset) * duration / TimeInterval(easeScale)
let scalingDuration = duration - easingDuration
UIView.animate(withDuration: scalingDuration, delay: 0.0, options: .curveEaseIn, animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: easeScale, y: easeScale)
}, completion: { (completed: Bool) -> Void in
UIView.animate(withDuration: easingDuration, delay: 0.0, options: .curveEaseOut, animations: { () -> Void in
self.transform = CGAffineTransform.identity
}, completion: { (completed: Bool) -> Void in
})
})
}
/**
Zoom out any view with specified offset magnification.
- parameter duration: animation duration.
- parameter easingOffset: easing offset.
*/
func zoomOutWithEasing(duration: TimeInterval = 0.2, easingOffset: CGFloat = 0.2) {
let easeScale = 1.0 + easingOffset
let easingDuration = TimeInterval(easingOffset) * duration / TimeInterval(easeScale)
let scalingDuration = duration - easingDuration
UIView.animate(withDuration: easingDuration, delay: 0.0, options: .curveEaseOut, animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: easeScale, y: easeScale)
}, completion: { (completed: Bool) -> Void in
UIView.animate(withDuration: scalingDuration, delay: 0.0, options: .curveEaseOut, animations: { () -> Void in
self.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
}, completion: { (completed: Bool) -> Void in
})
})
}
}
Swift 3.x+
extension UIButton {
func pulsate() {
let pulse = CASpringAnimation(keyPath: "transform.scale")
pulse.duration = 0.2
pulse.fromValue = 0.95
pulse.toValue = 1.0
pulse.autoreverses = true
pulse.repeatCount = 2
pulse.initialVelocity = 0.5
pulse.damping = 1.0
layer.add(pulse, forKey: "pulse")
}
func flash() {
let flash = CABasicAnimation(keyPath: "opacity")
flash.duration = 0.2
flash.fromValue = 1
flash.toValue = 0.1
flash.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
flash.autoreverses = true
flash.repeatCount = 3
layer.add(flash, forKey: nil)
}
func shake() {
let shake = CABasicAnimation(keyPath: "position")
shake.duration = 0.05
shake.repeatCount = 2
shake.autoreverses = true
let fromPoint = CGPoint(x: center.x - 5, y: center.y)
let fromValue = NSValue(cgPoint: fromPoint)
let toPoint = CGPoint(x: center.x + 5, y: center.y)
let toValue = NSValue(cgPoint: toPoint)
shake.fromValue = fromValue
shake.toValue = toValue
layer.add(shake, forKey: "position")
}
}
Usage:
myButton.flash()
// myButton.pulsate()
// myButton.shake()
Credits: Sean Allen
Swift 3 Version:
UIView.animate(withDuration: 0.6, animations: {
button.transform = CGAffineTransform.identity.scaledBy(x: 0.6, y: 0.6)
}, completion: { (finish) in
UIView.animate(withDuration: 0.6, animations: {
button.transform = CGAffineTransform.identity
})
})
Using Swift 4 Xcode 9, This will animate the button down when initially pressed and then back up when released.
extension UIView {
func animateButtonDown() {
UIView.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseIn], animations: {
self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
}, completion: nil)
}
func animateButtonUp() {
UIView.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: {
self.transform = CGAffineTransform.identity
}, completion: nil)
}
Implementation:
#IBAction func buttonTouchDown(_ sender: UIButton) {
//Connected with Touch Down Action
sender.animateButtonDown()
}
#IBAction func buttonTouchUpOutside(_ sender: UIButton) {
//Connected with Touch Up Outside Action
//if touch moved away from button
sender.animateButtonUp()
}
#IBAction func buttonTouchUpInside(_ sender: UIButton) {
//Connected with Touch Up Inside Action
sender.animateButtonUp()
//code to execute when button pressed
}
It works with me as following, the animation is set to be small then when it start animation it get back to its original size:
Swift 2
button.transform = CGAffineTransformMakeScale(0.6, 0.6)
UIView.animateWithDuration(0.3, animations: { () -> Void in
button.transform = CGAffineTransformMakeScale(1,1)
})
Swift 3, 4, 5
button.transform = CGAffineTransform.init(scaleX: 0.6, y: 0.6)
UIView.animate(withDuration: 0.3, animations: { () -> Void in
button.transform = CGAffineTransform.init(scaleX: 1, y: 1)
})
I prefer to have the press animation and set it more fast than the other examples, with the completion control for waiting until the animation is ended:
Swift 3:
extension UIButton {
func press(completion:#escaping ((Bool) -> Void)) {
UIView.animate(withDuration: 0.05, animations: {
self.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) }, completion: { (finish: Bool) in
UIView.animate(withDuration: 0.1, animations: {
self.transform = CGAffineTransform.identity
completion(finish)
})
})
}
}
Usage:
#IBAction func playPauseBtnTap(_ sender: Any) {
let playPauseBtn = sender as! UIButton
playPauseBtn.press(completion:{ finish in
if finish {
print("animation ended")
}
}
}
Using the following animation the button will start from its full size, decrease to 0.6 with a spring animation to bounce back to it's full size.
[UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.4 initialSpringVelocity:0.3 options:0 animations:^{
//Animations
button.transform = CGAffineTransformIdentity;
CGAffineTransformMakeScale(0.6, 0.6)
} completion:^(BOOL finished) {
//Completion Block
[UIView.animateWithDuration(0.5){
button.transform = CGAffineTransformIdentity
}];
}];
You can try this if you want a Autoreverse effect with a completion handler.
viewToAnimate.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
UIView.animate(withDuration: 0.7, // your duration
delay: 0,
usingSpringWithDamping: 0.2,
initialSpringVelocity: 6.0,
animations: { _ in
viewToAnimate.transform = .identity
},
completion: { _ in
// Implement your awesome logic here.
})
iOS 9 and xCode 7
//for zoom in
[UIView animateWithDuration:0.5f animations:^{
self.sendButton.transform = CGAffineTransformMakeScale(1.5, 1.5);
} completion:^(BOOL finished){}];
// for zoom out
[UIView animateWithDuration:0.5f animations:^{
self.sendButton.transform = CGAffineTransformMakeScale(1, 1);
}completion:^(BOOL finished){}];
This will give a wonderful bouncing effect:
#IBAction func TouchUpInsideEvent(sender: UIButton) {
UIView.animateWithDuration(2.0,
delay: 0,
usingSpringWithDamping: CGFloat(0.20),
initialSpringVelocity: CGFloat(6.0),
options: UIViewAnimationOptions.AllowUserInteraction,
animations: {
sender.transform = CGAffineTransformIdentity
},
completion: { Void in() }
)
}
#IBAction func touchDownEvent(sender: UIButton) {
UIView.animateWithDuration(0.15, animations: {
sender.transform = CGAffineTransformMakeScale(0.6, 0.6)
})
}
Scaling Button or any view about three times or more use following code. swift 3 or swift 4 with xcode 9.
UIView.animate(withDuration: 0.2, animations: {
self.cartShowHideBtnView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
}, completion: { (finish: Bool) in
UIView.animate(withDuration: 0.2, animations: {
self.cartShowHideBtnView.transform = CGAffineTransform.identity
}, completion:{(finish: Bool) in
UIView.animate(withDuration: 0.2, animations: {
self.cartShowHideBtnView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
}, completion: { (finish: Bool) in
UIView.animate(withDuration: 0.2, animations: {
self.cartShowHideBtnView.transform = CGAffineTransform.identity
}, completion:{(finish: Bool) in
UIView.animate(withDuration: 0.2, animations: {
self.cartShowHideBtnView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
}, completion: { (finish: Bool) in
UIView.animate(withDuration: 0.2, animations: {
self.cartShowHideBtnView.transform = CGAffineTransform.identity
})
})
})
})
})
})
I did a protocol using Swift 4, that you can use at some specifics UIViews that you want to animate... You can try some animations over here or change time and delay.
This way is recommended because you can use this protocol and others at one view and this view can use this functions, doing a lot os extensions from UIView create code smell.
import Foundation
import UIKit
protocol Showable where Self: UIView {}
extension Showable {
func show(_ view: UIView? = nil) {
if let view = view {
self.animate(view)
} else {
self.animate(self)
}
}
private func animate(_ view: UIView) {
view.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
UIView.animate(withDuration: 2.0,
delay: 0,
usingSpringWithDamping: CGFloat(0.20),
initialSpringVelocity: CGFloat(6.0),
options: [.allowUserInteraction],
animations: {
view.transform = CGAffineTransform.identity
})
}
}
Here is a working example :
extension UIButton{
func flash() {
let flash = CABasicAnimation(keyPath: "opacity")
flash.duration = 0.5
flash.fromValue = 1
flash.toValue = 0.1
flash.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
flash.autoreverses = true
flash.repeatCount = 3
layer.add(flash, forKey: nil)
}
}
#IBAction func taptosave(_ sender: UIButton) {
sender.flash()
}