Related
I'm trying to make UIAlert shake when the textfield in the alert is empty, I use this textfield to add items to my array, and with the code I've used it doesn't add any empty values to my list but nor stay on UIAlertAction, so obviously the shaking animation doesn't show too, this is my code:
let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
if textField.text?.isEmpty ?? true {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: textField.center.x - 10, y: textField.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: textField.center.x + 10, y: textField.center.y))
textField.layer.add(animation, forKey: "position")
} else {
self.itemArray.append(textField.text!)
self.tableView.reloadData()
}
}
Two things here, first just to help you here i'm adding a extension to shake functionality:
extension UIView {
func shake(duration timeDuration: Double = 0.07, repeat countRepeat: Float = 3){
let animation = CABasicAnimation(keyPath: "position")
animation.duration = timeDuration
animation.repeatCount = countRepeat
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 10, y: self.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 10, y: self.center.y))
self.layer.add(animation, forKey: "position")
}
}
So for you to use can be easy like, "your view".shake() it's more simple.
Second and most important is, you can't use this, or even the way that you were trying, because UITextField inherit from UIControl, and need to be from UIView, so the adjust for you is, just put your UITextFiel inside a UIView and add the shake functionality to the UIView and them will shake everything inside :D
If like up vote :D :D
I am trying to figure out how to make the text Field shake on button press when the user leaves the text field blank.
I currently have the following code working:
if self.subTotalAmountData.text == "" {
let alertController = UIAlertController(title: "Title", message:
"What is the Sub-Total!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Okay", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
} else {
}
But i think it would be much more appealing to just have the text field shake as an alert.
I can't find anything to animate the text field.
Any ideas?
Thanks!
You can change the duration and repeatCount and tweak it. This is what I use in my code. Varying the fromValue and toValue will vary the distance moved in the shake.
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: viewToShake.center.x - 10, y: viewToShake.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: viewToShake.center.x + 10, y: viewToShake.center.y))
viewToShake.layer.add(animation, forKey: "position")
The following function is used in any view.
extension UIView {
func shake() {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = 0.6
animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ]
layer.add(animation, forKey: "shake")
}
}
EDIT: using CABasicAnimation cause the app to crash if you ever trigger the animation twice in a row. So be sure to use CAKeyframeAnimation. Bug has been fixed, thanks to the comments :)
Or you can use this if you want more parameters (in swift 5) :
public extension UIView {
func shake(count : Float = 4,for duration : TimeInterval = 0.5,withTranslation translation : Float = 5) {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.repeatCount = count
animation.duration = duration/TimeInterval(animation.repeatCount)
animation.autoreverses = true
animation.values = [translation, -translation]
layer.add(animation, forKey: "shake")
}
}
You can call this function on any UIView, UIButton, UILabel, UITextView etc. This way
yourView.shake()
Or this way if you want to add some custom parameters to the animation:
yourView.shake(count: 5, for: 1.5, withTranslation: 10)
I think all of these are dangerous.
If your shake animation is based on a user action and that user action is triggered while animating.
CRAAAAAASH
Here is my way in Swift 4:
static func shake(view: UIView, for duration: TimeInterval = 0.5, withTranslation translation: CGFloat = 10) {
let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.3) {
view.transform = CGAffineTransform(translationX: translation, y: 0)
}
propertyAnimator.addAnimations({
view.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: 0.2)
propertyAnimator.startAnimation()
}
Maybe not the cleanest, but this method can be triggered repeatedly and is easily understood
Edit:
I am a huge proponent for usage of UIViewPropertyAnimator. So many cool features that allow you to make dynamic modifications to basic animations.
Here is another example to add a red border while the view is shaking, then removing it when the shake finishes.
static func shake(view: UIView, for duration: TimeInterval = 0.5, withTranslation translation: CGFloat = 10) {
let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.3) {
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 1
view.transform = CGAffineTransform(translationX: translation, y: 0)
}
propertyAnimator.addAnimations({
view.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: 0.2)
propertyAnimator.addCompletion { (_) in
view.layer.borderWidth = 0
}
propertyAnimator.startAnimation()
}
Swift 5.0
extension UIView {
func shake(){
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 3
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 10, y: self.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 10, y: self.center.y))
self.layer.add(animation, forKey: "position")
}
}
To use
self.vwOffer.shake()
Swift 5
Safe (non crash) shake extension for Corey Pett answer:
extension UIView {
func shake(for duration: TimeInterval = 0.5, withTranslation translation: CGFloat = 10) {
let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.3) {
self.transform = CGAffineTransform(translationX: translation, y: 0)
}
propertyAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: 0.2)
propertyAnimator.startAnimation()
}
}
extension CALayer {
func shake(duration: NSTimeInterval = NSTimeInterval(0.5)) {
let animationKey = "shake"
removeAnimationForKey(animationKey)
let kAnimation = CAKeyframeAnimation(keyPath: "transform.translation.x")
kAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
kAnimation.duration = duration
var needOffset = CGRectGetWidth(frame) * 0.15,
values = [CGFloat]()
let minOffset = needOffset * 0.1
repeat {
values.append(-needOffset)
values.append(needOffset)
needOffset *= 0.5
} while needOffset > minOffset
values.append(0)
kAnimation.values = values
addAnimation(kAnimation, forKey: animationKey)
}
}
How to use:
[UIView, UILabel, UITextField, UIButton & etc].layer.shake(NSTimeInterval(0.7))
I tried some of the available solutions but none of them were handling the full shake animation: moving from left to right and get back to the original position.
So, after some investigation I found the right solution that I consider to be a successful shake using UIViewPropertyAnimator.
func shake(completion: (() -> Void)? = nil) {
let speed = 0.75
let time = 1.0 * speed - 0.15
let timeFactor = CGFloat(time / 4)
let animationDelays = [timeFactor, timeFactor * 2, timeFactor * 3]
let shakeAnimator = UIViewPropertyAnimator(duration: time, dampingRatio: 0.3)
// left, right, left, center
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 20, y: 0)
})
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: -20, y: 0)
}, delayFactor: animationDelays[0])
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 20, y: 0)
}, delayFactor: animationDelays[1])
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: animationDelays[2])
shakeAnimator.startAnimation()
shakeAnimator.addCompletion { _ in
completion?()
}
shakeAnimator.startAnimation()
}
Final result:
func shakeTextField(textField: UITextField)
{
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 3
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: textField.center.x - 10, y: textField.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: textField.center.x + 10, y: textField.center.y))
textField.layer.add(animation, forKey: "position")
textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "",
attributes: [NSAttributedStringKey.foregroundColor: UIColor.red])
}
//write in base class or any view controller and use it
This is based on CABasicAnimation, it contain also an audio effect :
extension UIView{
var audioPlayer = AVAudioPlayer()
func vibrate(){
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 5.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 5.0, self.center.y))
self.layer.addAnimation(animation, forKey: "position")
// audio part
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(mySoundFileName, ofType: "mp3")!))
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("∙ Error playing vibrate sound..")
}
}
}
func addShakeAnimation(duration: CGFloat = 0.3, repeatCount: Float = 4, angle: Float = Float.pi / 27, completion: (() -> Void)? = nil) {
let rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z")
rotationAnimation.duration = TimeInterval(duration/CGFloat(repeatCount))
rotationAnimation.repeatCount = repeatCount
rotationAnimation.autoreverses = true
rotationAnimation.fromValue = -angle
rotationAnimation.toValue = angle
rotationAnimation.isRemovedOnCompletion = true
CATransaction.begin()
CATransaction.setCompletionBlock {
if let completion = completion {
completion()
}
}
layer.add(rotationAnimation, forKey: "shakeAnimation")
CATransaction.commit()
}
I am trying to animate the position of a UIView object with CABasicAnimation on a button Tap. The object animated and moves to the 'to' position, but returns back to the original position after the animation ends. I want to retain the position of the view object even after the animation ends. This the code snippet that performs the animation. viewObject is the object which I'm trying to animate.
let animation = CABasicAnimation(keyPath: "position")
animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.86, 0, 0.07, 1.0)
animation.duration = 0.5
animation.fromValue = NSValue(cgPoint: CGPoint(x: viewObject.center.x, y: viewObject.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: viewObject.center.x + 64, y: viewObject.center.y))
viewObject.layer.add(animation, forKey: "position")
add following lines before adding animation
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
Swift
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
Please add the following code:
Objective-C:
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
Swift 4:
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
I think you need to give frame again on completion. So, this can be a nice approach
UIView.animate(withDuration: 0.7, delay: 1.0, options: .curveLinear, animations: {
let animation = CABasicAnimation(keyPath: "position")
animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.86, 0, 0.07, 1.0)
animation.duration = 0.5
animation.fromValue = NSValue(cgPoint: CGPoint(x: self.viewObject.center.x, y: self.viewObject.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: self.viewObject.center.x + 64, y: self.viewObject.center.y))
self.viewObject.layer.add(animation, forKey: "position")
}, completion: { finished in
self.viewObject.frame.origin.x = self.viewObject.frame.origin.x + 64
})
Try this. It will work perfectly
UIView.animateWithDuration(0.7, delay: 1.0, options: .CurveEaseOut, animations:
{
Code
}, completion:
{
finished in
println("Nothing to do!")
})
Just do animation inside a block and when it finishes. hold that position and do not revert back. It should stay at same position
and add this line inside code
cabasicanimation.removedOnCompletion = false;
This line will make it do not go back in the same state
If you want keep your position, you should put your code same my structure
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.setCompletionBlock {
// Input code set new position at here
}
// Intput code animation
CATransaction.commit()
I have a CALayer and I've added a CABasicAnimation to it like this:
circle = CALayer()
circle.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
circle.backgroundColor = UIColor.green().cgColor
circle.cornerRadius = 50
circle.speed = 0
view.layer.addSublayer(circle)
animation = CABasicAnimation(keyPath: "position")
animation.duration = 1
animation.fromValue = NSValue(cgPoint: CGPoint(x: 50, y: 50))
animation.toValue = NSValue(cgPoint: CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2))
animation.fillMode = kCAFillModeBoth
animation.isRemovedOnCompletion = false
circle.add(animation, forKey: nil)
When I play the animation backwards by setting the layer's speed to -1,
the layer disappears at the end of the animation:
circle.timeOffset = 1
circle.speed = -1
circle.beginTime = CACurrentMediaTime()
Because of that, I use CADisplayLink to play an animation backwards,
but I'm wondering if it can be achieved by setting the speed to -1 and preventing the layer from disappearing also.
I am trying to figure out how to make the text Field shake on button press when the user leaves the text field blank.
I currently have the following code working:
if self.subTotalAmountData.text == "" {
let alertController = UIAlertController(title: "Title", message:
"What is the Sub-Total!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Okay", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
} else {
}
But i think it would be much more appealing to just have the text field shake as an alert.
I can't find anything to animate the text field.
Any ideas?
Thanks!
You can change the duration and repeatCount and tweak it. This is what I use in my code. Varying the fromValue and toValue will vary the distance moved in the shake.
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: viewToShake.center.x - 10, y: viewToShake.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: viewToShake.center.x + 10, y: viewToShake.center.y))
viewToShake.layer.add(animation, forKey: "position")
The following function is used in any view.
extension UIView {
func shake() {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = 0.6
animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ]
layer.add(animation, forKey: "shake")
}
}
EDIT: using CABasicAnimation cause the app to crash if you ever trigger the animation twice in a row. So be sure to use CAKeyframeAnimation. Bug has been fixed, thanks to the comments :)
Or you can use this if you want more parameters (in swift 5) :
public extension UIView {
func shake(count : Float = 4,for duration : TimeInterval = 0.5,withTranslation translation : Float = 5) {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.repeatCount = count
animation.duration = duration/TimeInterval(animation.repeatCount)
animation.autoreverses = true
animation.values = [translation, -translation]
layer.add(animation, forKey: "shake")
}
}
You can call this function on any UIView, UIButton, UILabel, UITextView etc. This way
yourView.shake()
Or this way if you want to add some custom parameters to the animation:
yourView.shake(count: 5, for: 1.5, withTranslation: 10)
I think all of these are dangerous.
If your shake animation is based on a user action and that user action is triggered while animating.
CRAAAAAASH
Here is my way in Swift 4:
static func shake(view: UIView, for duration: TimeInterval = 0.5, withTranslation translation: CGFloat = 10) {
let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.3) {
view.transform = CGAffineTransform(translationX: translation, y: 0)
}
propertyAnimator.addAnimations({
view.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: 0.2)
propertyAnimator.startAnimation()
}
Maybe not the cleanest, but this method can be triggered repeatedly and is easily understood
Edit:
I am a huge proponent for usage of UIViewPropertyAnimator. So many cool features that allow you to make dynamic modifications to basic animations.
Here is another example to add a red border while the view is shaking, then removing it when the shake finishes.
static func shake(view: UIView, for duration: TimeInterval = 0.5, withTranslation translation: CGFloat = 10) {
let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.3) {
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 1
view.transform = CGAffineTransform(translationX: translation, y: 0)
}
propertyAnimator.addAnimations({
view.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: 0.2)
propertyAnimator.addCompletion { (_) in
view.layer.borderWidth = 0
}
propertyAnimator.startAnimation()
}
Swift 5.0
extension UIView {
func shake(){
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 3
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 10, y: self.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 10, y: self.center.y))
self.layer.add(animation, forKey: "position")
}
}
To use
self.vwOffer.shake()
Swift 5
Safe (non crash) shake extension for Corey Pett answer:
extension UIView {
func shake(for duration: TimeInterval = 0.5, withTranslation translation: CGFloat = 10) {
let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.3) {
self.transform = CGAffineTransform(translationX: translation, y: 0)
}
propertyAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: 0.2)
propertyAnimator.startAnimation()
}
}
extension CALayer {
func shake(duration: NSTimeInterval = NSTimeInterval(0.5)) {
let animationKey = "shake"
removeAnimationForKey(animationKey)
let kAnimation = CAKeyframeAnimation(keyPath: "transform.translation.x")
kAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
kAnimation.duration = duration
var needOffset = CGRectGetWidth(frame) * 0.15,
values = [CGFloat]()
let minOffset = needOffset * 0.1
repeat {
values.append(-needOffset)
values.append(needOffset)
needOffset *= 0.5
} while needOffset > minOffset
values.append(0)
kAnimation.values = values
addAnimation(kAnimation, forKey: animationKey)
}
}
How to use:
[UIView, UILabel, UITextField, UIButton & etc].layer.shake(NSTimeInterval(0.7))
I tried some of the available solutions but none of them were handling the full shake animation: moving from left to right and get back to the original position.
So, after some investigation I found the right solution that I consider to be a successful shake using UIViewPropertyAnimator.
func shake(completion: (() -> Void)? = nil) {
let speed = 0.75
let time = 1.0 * speed - 0.15
let timeFactor = CGFloat(time / 4)
let animationDelays = [timeFactor, timeFactor * 2, timeFactor * 3]
let shakeAnimator = UIViewPropertyAnimator(duration: time, dampingRatio: 0.3)
// left, right, left, center
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 20, y: 0)
})
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: -20, y: 0)
}, delayFactor: animationDelays[0])
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 20, y: 0)
}, delayFactor: animationDelays[1])
shakeAnimator.addAnimations({
self.transform = CGAffineTransform(translationX: 0, y: 0)
}, delayFactor: animationDelays[2])
shakeAnimator.startAnimation()
shakeAnimator.addCompletion { _ in
completion?()
}
shakeAnimator.startAnimation()
}
Final result:
func shakeTextField(textField: UITextField)
{
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 3
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: textField.center.x - 10, y: textField.center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: textField.center.x + 10, y: textField.center.y))
textField.layer.add(animation, forKey: "position")
textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "",
attributes: [NSAttributedStringKey.foregroundColor: UIColor.red])
}
//write in base class or any view controller and use it
This is based on CABasicAnimation, it contain also an audio effect :
extension UIView{
var audioPlayer = AVAudioPlayer()
func vibrate(){
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 5.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 5.0, self.center.y))
self.layer.addAnimation(animation, forKey: "position")
// audio part
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(mySoundFileName, ofType: "mp3")!))
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("∙ Error playing vibrate sound..")
}
}
}
func addShakeAnimation(duration: CGFloat = 0.3, repeatCount: Float = 4, angle: Float = Float.pi / 27, completion: (() -> Void)? = nil) {
let rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z")
rotationAnimation.duration = TimeInterval(duration/CGFloat(repeatCount))
rotationAnimation.repeatCount = repeatCount
rotationAnimation.autoreverses = true
rotationAnimation.fromValue = -angle
rotationAnimation.toValue = angle
rotationAnimation.isRemovedOnCompletion = true
CATransaction.begin()
CATransaction.setCompletionBlock {
if let completion = completion {
completion()
}
}
layer.add(rotationAnimation, forKey: "shakeAnimation")
CATransaction.commit()
}