Last active
May 19, 2019 14:43
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIView+Wiggle.h | |
// | |
@interface UIView (Wiggle) | |
- (void)wiggle; | |
- (void)endWiggling; | |
- (BOOL)isWiggling; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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