import Foundation import UIKit // Incuding at least one Objective-C class in a swift file ensures that it doesn't get stripped by the linker private final class LinkHelperClass: NSObject { } public extension CALayer { func addShakeAnimation(amplitude: CGFloat = 3.0, duration: Double = 0.3, count: Int = 4, decay: Bool = false) { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 if k != 0 && k != 1 { speed = Float(1.0) / k } let animation = CAKeyframeAnimation(keyPath: "position.x") var values: [CGFloat] = [] values.append(0.0) for i in 0 ..< count { let sign: CGFloat = (i % 2 == 0) ? 1.0 : -1.0 let multiplier = decay ? 1.0 / CGFloat(i + 1) : 1.0 values.append(amplitude * sign * multiplier) } values.append(0.0) animation.values = values.map { ($0 as NSNumber) as AnyObject } var keyTimes: [NSNumber] = [] for i in 0 ..< values.count { if i == 0 { keyTimes.append(0.0) } else if i == values.count - 1 { keyTimes.append(1.0) } else { keyTimes.append((Double(i) / Double(values.count - 1)) as NSNumber) } } animation.keyTimes = keyTimes animation.speed = speed animation.duration = duration animation.isAdditive = true self.add(animation, forKey: "shake") } func addReorderingShaking() { func degreesToRadians(_ x: CGFloat) -> CGFloat { return .pi * x / 180.0 } let duration: Double = 0.4 let displacement: CGFloat = 1.0 let degreesRotation: CGFloat = 2.0 let negativeDisplacement = -1.0 * displacement let position = CAKeyframeAnimation.init(keyPath: "position") position.beginTime = 0.8 position.duration = duration position.values = [ NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)), NSValue(cgPoint: CGPoint(x: 0, y: 0)), NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)), NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)), NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)) ] position.calculationMode = .linear position.isRemovedOnCompletion = false position.repeatCount = Float.greatestFiniteMagnitude position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) position.isAdditive = true let transform = CAKeyframeAnimation.init(keyPath: "transform") transform.beginTime = 2.6 transform.duration = 0.3 transform.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ) transform.values = [ degreesToRadians(-1.0 * degreesRotation), degreesToRadians(degreesRotation), degreesToRadians(-1.0 * degreesRotation) ] transform.calculationMode = .linear transform.isRemovedOnCompletion = false transform.repeatCount = Float.greatestFiniteMagnitude transform.isAdditive = true transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) self.add(position, forKey: "shaking_position") self.add(transform, forKey: "shaking_rotation") } }