iOS icon jiggle algorithm
@Vic320's answer is good but personally I don't like the translation.I've edited his code to provide a solution that I personally feel looks more like the springboard wobble effect. Mostly, it's achieved by adding a little randomness and focusing on rotation, without translation:
#define degreesToRadians(x) (M_PI * (x) / 180.0)#define kAnimationRotateDeg 1.0- (void)startJiggling { NSInteger randomInt = arc4random_uniform(500); float r = (randomInt/500.0)+0.5; CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( (kAnimationRotateDeg * -1.0) - r )); CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg + r )); self.transform = leftWobble; // starting point [[self layer] setAnchorPoint:CGPointMake(0.5, 0.5)]; [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{ [UIView setAnimationRepeatCount:NSNotFound]; self.transform = rightWobble; } completion:nil];}- (void)stopJiggling { [self.layer removeAllAnimations]; self.transform = CGAffineTransformIdentity;}
Credit where credit's due though, @Vic320's answer provided the basis for this code so +1 for that.
OK, so the openspringboard code didn't quite do it for me but I did allow me to create some code that I think is a bit better, still not prefect but better. If anyone has suggestions to make this better, I would love to hear them... (add this to the subclass of the view(s) you want to jiggle)
#define degreesToRadians(x) (M_PI * (x) / 180.0)#define kAnimationRotateDeg 1.0#define kAnimationTranslateX 2.0#define kAnimationTranslateY 2.0- (void)startJiggling:(NSInteger)count { CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? +1 : -1 ) )); CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? -1 : +1 ) )); CGAffineTransform moveTransform = CGAffineTransformTranslate(rightWobble, -kAnimationTranslateX, -kAnimationTranslateY); CGAffineTransform conCatTransform = CGAffineTransformConcat(rightWobble, moveTransform); self.transform = leftWobble; // starting point [UIView animateWithDuration:0.1 delay:(count * 0.08) options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{ self.transform = conCatTransform; } completion:nil];}- (void)stopJiggling { [self.layer removeAllAnimations]; self.transform = CGAffineTransformIdentity; // Set it straight }
@mientus Original Apple Jiggle code in Swift 4, with optional parameters to adjust the duration (i.e. speed), displacement (i.e. position change) and degrees (i.e. rotation amount).
private func degreesToRadians(_ x: CGFloat) -> CGFloat { return .pi * x / 180.0}func startWiggle( duration: Double = 0.25, displacement: CGFloat = 1.0, 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 = duration transform.valueFunction = CAValueFunction(name: kCAValueFunctionRotateZ) 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.layer.add(position, forKey: nil) self.layer.add(transform, forKey: nil)}