#import "TGAnimationUtils.h" #import NSString *kCAMediaTimingFunctionSpring = @"kCAMediaTimingFunctionSpring"; @interface TGLayerAnimationDelegate : NSObject { void (^_completion)(bool); } @end @implementation TGLayerAnimationDelegate - (instancetype)initWithCompletion:(void (^)(bool))completion { self = [super init]; if (self != nil) { _completion = [completion copy]; } return self; } - (void)animationDidStop:(__unused CAAnimation *)anim finished:(BOOL)flag { if (_completion) { _completion(flag); } } @end @interface CAAnimation (AnimationUtils) @end @implementation CAAnimation (AnimationUtils) - (void)setCompletionBlock:(void (^)(bool))block { self.delegate = [[TGLayerAnimationDelegate alloc] initWithCompletion:block]; } @end static CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath]; springAnimation.mass = 3.0f; springAnimation.stiffness = 1000.0f; springAnimation.damping = 500.0f; springAnimation.duration = 0.5;//springAnimation.settlingDuration; springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; } static CABasicAnimation * _Nonnull makeExtendedSpringAnimation(NSString * _Nonnull keyPath, CGFloat damping) { CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath]; springAnimation.mass = 3.0f; springAnimation.stiffness = 1000.0f; springAnimation.damping = damping; springAnimation.duration = 0.5;//springAnimation.settlingDuration; springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; } @implementation CALayer (AnimationUtils) - (void)animateFrom:(id)from to:(id)to keyPath:(NSString *)keyPath timingFunction:(NSString *)timingFunction duration:(NSTimeInterval)duration removeOnCompletion:(bool)removeOnCompletion completion:(void (^)(bool))completion { if ([timingFunction isEqualToString:kCAMediaTimingFunctionSpring]) { CABasicAnimation *animation = makeSpringAnimation(keyPath); animation.fromValue = from; animation.toValue = to; animation.removedOnCompletion = removeOnCompletion; animation.fillMode = kCAFillModeForwards; if (completion != nil) { [animation setCompletionBlock:completion]; } float k = (float)TGAnimationSpeedFactor(); float speed = 1.0f; if (k != 0 && k != 1) { speed = 1.0f / k; } animation.speed = speed * (float)(animation.duration / duration); [self addAnimation:animation forKey:keyPath]; } else { float k = (float)TGAnimationSpeedFactor(); float speed = 1.0f; if (k != 0 && k != 1) { speed = 1.0f / k; } CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; animation.fromValue = from; animation.toValue = to; animation.duration = duration; animation.timingFunction = [CAMediaTimingFunction functionWithName: timingFunction]; animation.removedOnCompletion = removeOnCompletion; animation.fillMode = kCAFillModeForwards; animation.speed = speed; if (completion != nil) { [animation setCompletionBlock:completion]; } [self addAnimation:animation forKey:keyPath]; } } - (void)animateSpringFrom:(id)from to:(id)to keyPath:(NSString *)keyPath duration:(NSTimeInterval)duration removeOnCompletion:(bool)removeOnCompletion completion:(void (^)(bool))completion { CABasicAnimation *animation = makeExtendedSpringAnimation(keyPath, 75.0f); animation.fromValue = from; animation.toValue = to; animation.removedOnCompletion = removeOnCompletion; animation.fillMode = kCAFillModeForwards; if (completion != nil) { [animation setCompletionBlock:completion]; } float k = (float)TGAnimationSpeedFactor(); float speed = 1.0f; if (k != 0 && k != 1) { speed = 1.0f / k; } animation.speed = speed * (float)(animation.duration / duration); [self addAnimation:animation forKey:keyPath]; } - (void)animateAlphaFrom:(CGFloat)from to:(CGFloat)to duration:(NSTimeInterval)duration timingFunction:(NSString *)timingFunction removeOnCompletion:(bool)removeOnCompletion completion:(void (^)(bool))completion { [self animateFrom:@(from) to:@(to) keyPath:@"opacity" timingFunction:timingFunction duration:duration removeOnCompletion:removeOnCompletion completion:completion]; } - (void)animateScaleFrom:(CGFloat)from to:(CGFloat)to duration:(NSTimeInterval)duration timingFunction:(NSString *)timingFunction removeOnCompletion:(bool)removeOnCompletion completion:(void (^)(bool))completion { [self animateFrom:@(from) to:@(to) keyPath:@"transform.scale" timingFunction:timingFunction duration:duration removeOnCompletion:removeOnCompletion completion:completion]; } - (void)animateSpringScaleFrom:(CGFloat)from to:(CGFloat)to duration:(NSTimeInterval)duration removeOnCompletion:(bool)removeOnCompletion completion:(void (^)(bool))completion { [self animateSpringFrom:@(from) to:@(to) keyPath:@"transform.scale" duration:duration removeOnCompletion:removeOnCompletion completion:completion]; } - (void)animatePositionFrom:(CGPoint)from to:(CGPoint)to duration:(NSTimeInterval)duration timingFunction:(NSString *)timingFunction removeOnCompletion:(bool)removeOnCompletion completion:(void (^)(bool))completion { [self animateFrom:[NSValue valueWithCGPoint:from] to:[NSValue valueWithCGPoint:to] keyPath:@"position" timingFunction:timingFunction duration:duration removeOnCompletion:removeOnCompletion completion:completion]; } @end