Shake Animation for UITextField/UIView in Swift

后端 未结 10 1373
梦毁少年i
梦毁少年i 2020-12-07 09:37

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:

相关标签:
10条回答
  • 2020-12-07 10:07

    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()
    }
    
    0 讨论(0)
  • 2020-12-07 10:07

    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()
    
    0 讨论(0)
  • 2020-12-07 10:12

    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:

    0 讨论(0)
  • 2020-12-07 10:19

    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)
    
    0 讨论(0)
  • 2020-12-07 10:19

    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..")
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-07 10:20

    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")
    
    0 讨论(0)
提交回复
热议问题