Skip to content

Instantly share code, notes, and snippets.

@yaronalk
Last active May 19, 2019 14:43
Show Gist options
  • Save yaronalk/8bbe8402e467de5a740b51ddca545cfb to your computer and use it in GitHub Desktop.
Save yaronalk/8bbe8402e467de5a740b51ddca545cfb to your computer and use it in GitHub Desktop.
Make any UIView (e.g., UICollectionViewCell) wiggle like iOS home screen apps before being deleted
//
// UIView+Wiggle.h
//
@interface UIView (Wiggle)
- (void)wiggle;
- (void)endWiggling;
- (BOOL)isWiggling;
@end
//
// UIView+Wiggle.m
//
static NSString * const kWiggleRotation = @"wiggleRotation";
static NSString * const kWigglePosition = @"wigglePosition";
// default values
static CGFloat const kVWiggleVariance = 0.16;
static CGFloat const kMinAngle = 1.00;
static CGFloat const kRotationDuration = 0.19;
static CGFloat const kPositionDuration = 0.55;
static CGFloat const kPositionAnimationValues[] = {
-0.20f,
0.60f,
-0.40f,
0.46f,
-0.32f
}; // size must be equal to kPositionAnimationKeyTimes
static CGFloat const kPositionAnimationKeyTimes[] = {
0.f/6.f, // must be 0
1.f/6.f,
3.f/6.f,
5.f/6.f,
6.f/6.f // must be 1
}; // size must be equal to kPositionAnimationValues
#import "UIView+Wiggle.m"
@implementation UIView (Wiggle)
- (void)wiggle {
[self wiggleWithVariance:kVWiggleVariance
minAngle:kMinAngle
rotationDuration:kRotationDuration
positionDuration:kPositionDuration];
}
- (void)wiggleWithVariance:(CGFloat)variance
minAngle:(CGFloat)minAngle
rotationDuration:(CGFloat)rotationDuration
positionDuration:(CGFloat)positionDuration {
if (self.isWiggling) {
[self endWiggling];
}
// ROTATION
CABasicAnimation *rotateAnimation = CABasicAnimation.animation;
rotateAnimation.keyPath = @"transform.rotation";
CGFloat random = arc4random_uniform(100) / 100.f;
CGFloat appliedVariance = variance * random;
rotateAnimation.duration = rotationDuration * (1 - variance) + (rotationDuration * appliedVariance);
rotateAnimation.repeatCount = CGFLOAT_MAX;
rotateAnimation.autoreverses = YES;
CGFloat extraAngle = minAngle * random;
CGFloat fromAngle = (minAngle - extraAngle) * M_PI / 180;
CGFloat toAngle = (minAngle + extraAngle) * M_PI / 180;
rotateAnimation.fromValue = @(-fromAngle);
rotateAnimation.toValue = @(toAngle);
[self.layer addAnimation:rotateAnimation forKey:kWiggleRotation];
// POSITION
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animation];
positionAnimation.keyPath = @"position.x";
positionAnimation.duration = positionDuration * (1 - variance) + (positionDuration * appliedVariance);
positionAnimation.repeatCount = CGFLOAT_MAX;
positionAnimation.autoreverses = YES;
positionAnimation.values = processedAnimationValues(appliedVariance);
positionAnimation.keyTimes = processedAnimationKeyTimes();
positionAnimation.additive = YES;
[self.layer addAnimation:positionAnimation forKey:kWigglePosition];
}
static NSArray* processedAnimationValues (CGFloat appliedVariance) {
int count = sizeof(kPositionAnimationValues) / sizeof(kPositionAnimationValues[0]);
NSMutableArray *animationValuesArray = NSMutableArray.array;
for (int i = 0; i < count; i++) {
CGFloat rawValue = kPositionAnimationValues[i];
CGFloat variedValue = rawValue + (rawValue * appliedVariance);
[animationValuesArray addObject:@(variedValue)];
}
return [animationValuesArray copy];
}
static NSArray* processedAnimationKeyTimes () {
CGFloat random = arc4random_uniform(8)/100.f;
int count = sizeof(kPositionAnimationKeyTimes) / sizeof(kPositionAnimationKeyTimes[0]);
NSMutableArray *animationKeyTimesArray = NSMutableArray.array;
for (int i = 0; i < count; i++) {
CGFloat rawValue = kPositionAnimationKeyTimes[i];
CGFloat variedValue = rawValue;
if (rawValue != 0 && rawValue != 1) {
int sign = arc4random_uniform(2) == 1 ? 1 : (-1);
variedValue += (sign * random);
variedValue = MIN(variedValue, 0.99f);
variedValue = MAX(variedValue, 0.01f);
}
[animationKeyTimesArray addObject:@(variedValue)];
}
return [animationKeyTimesArray copy];
}
- (BOOL)isWiggling {
for (NSString *animationKeys in @[kWiggleRotation,kWigglePosition]) {
if ([self.layer.animationKeys containsObject:animationKeys]) {
return YES;
}
}
return NO;
}
- (void)endWiggling {
for (NSString *wiggleAnimationKey in wiggleAnimations()) {
[self.layer removeAnimationForKey:wiggleAnimationKey];
}
}
static NSArray* wiggleAnimations () {
return @[
kWiggleRotation,
kWigglePosition
];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment