// // SCTapAnimationView.m // SCCamera // // Created by Alexander Grytsiuk on 8/26/15. // Copyright (c) 2015 Snapchat, Inc. All rights reserved. // #import "SCTapAnimationView.h" #import @import QuartzCore; static const CGFloat kSCAnimationStep = 0.167; static const CGFloat kSCInnerCirclePadding = 2.5; static const CGFloat kSCTapAnimationViewWidth = 55; static const CGFloat kSCOuterRingBorderWidth = 1; static NSString *const kSCOpacityAnimationKey = @"opacity"; static NSString *const kSCScaleAnimationKey = @"scale"; @implementation SCTapAnimationView { CALayer *_outerRing; CALayer *_innerCircle; } #pragma mark Class Methods + (instancetype)tapAnimationView { return [[self alloc] initWithFrame:CGRectMake(0, 0, kSCTapAnimationViewWidth, kSCTapAnimationViewWidth)]; } #pragma mark Life Cycle - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.userInteractionEnabled = NO; _outerRing = [CALayer layer]; _outerRing.backgroundColor = [UIColor clearColor].CGColor; _outerRing.borderColor = [UIColor whiteColor].CGColor; _outerRing.borderWidth = kSCOuterRingBorderWidth; _outerRing.shadowColor = [UIColor blackColor].CGColor; _outerRing.shadowOpacity = 0.4; _outerRing.shadowOffset = CGSizeMake(0.5, 0.5); _outerRing.opacity = 0.0; _outerRing.frame = self.bounds; _outerRing.cornerRadius = CGRectGetMidX(_outerRing.bounds); [self.layer addSublayer:_outerRing]; _innerCircle = [CALayer layer]; _innerCircle.backgroundColor = [UIColor whiteColor].CGColor; _innerCircle.opacity = 0.0; _innerCircle.frame = CGRectInset(self.bounds, kSCInnerCirclePadding, kSCInnerCirclePadding); _innerCircle.cornerRadius = CGRectGetMidX(_innerCircle.bounds); [self.layer addSublayer:_innerCircle]; } return self; } #pragma mark Public - (void)showWithCompletion:(SCTapAnimationViewCompletion)completion { [_outerRing removeAllAnimations]; [_innerCircle removeAllAnimations]; [CATransaction begin]; [CATransaction setCompletionBlock:^{ if (completion) { completion(self); } }]; [self addOuterRingOpacityAnimation]; [self addOuterRingScaleAnimation]; [self addInnerCircleOpacityAnimation]; [self addInnerCircleScaleAnimation]; [CATransaction commit]; } #pragma mark Private - (CAKeyframeAnimation *)keyFrameAnimationWithKeyPath:(NSString *)keyPath duration:(CGFloat)duration values:(NSArray *)values keyTimes:(NSArray *)keyTimes timingFunctions:(NSArray *)timingFunctions { CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keyPath]; keyframeAnimation.duration = duration; keyframeAnimation.values = values; keyframeAnimation.keyTimes = keyTimes; keyframeAnimation.timingFunctions = timingFunctions; keyframeAnimation.fillMode = kCAFillModeForwards; keyframeAnimation.removedOnCompletion = NO; return keyframeAnimation; } - (CABasicAnimation *)animationWithKeyPath:(NSString *)keyPath duration:(CGFloat)duration fromValue:(NSValue *)fromValue toValue:(NSValue *)toValue timingFunction:(CAMediaTimingFunction *)timingFunction { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; animation.duration = duration; animation.fromValue = fromValue; animation.toValue = toValue; animation.timingFunction = timingFunction; animation.fillMode = kCAFillModeForwards; animation.removedOnCompletion = NO; return animation; } - (void)addOuterRingOpacityAnimation { CAKeyframeAnimation *animation = [self keyFrameAnimationWithKeyPath:@keypath(_outerRing, opacity) duration:kSCAnimationStep * 5 values:@[ @0.0, @1.0, @1.0, @0.0 ] keyTimes:@[ @0.0, @0.2, @0.8, @1.0 ] timingFunctions:@[ [CAMediaTimingFunction functionWithControlPoints:0.0:0.0:0.0:1.0], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], ]]; [_outerRing addAnimation:animation forKey:kSCOpacityAnimationKey]; } - (void)addOuterRingScaleAnimation { CAKeyframeAnimation *animation = [self keyFrameAnimationWithKeyPath:@keypath(_innerCircle, transform) duration:kSCAnimationStep * 3 values:@[ [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.50, 0.50, 1.0)], [NSValue valueWithCATransform3D:CATransform3DIdentity], [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.83, 0.83, 1.0)], ] keyTimes:@[ @0.0, @0.66, @1.0 ] timingFunctions:@[ [CAMediaTimingFunction functionWithControlPoints:0.0:0.0:0.0:1.0], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], ]]; [_outerRing addAnimation:animation forKey:kSCScaleAnimationKey]; } - (void)addInnerCircleOpacityAnimation { CAKeyframeAnimation *animation = [self keyFrameAnimationWithKeyPath:@keypath(_innerCircle, opacity) duration:kSCAnimationStep * 3 values:@[ @0.0, @0.40, @0.0 ] keyTimes:@[ @0.0, @0.33, @1.0 ] timingFunctions:@[ [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], ]]; [_innerCircle addAnimation:animation forKey:kSCOpacityAnimationKey]; } - (void)addInnerCircleScaleAnimation { CABasicAnimation *animation = [self animationWithKeyPath:@keypath(_innerCircle, transform) duration:kSCAnimationStep * 2 fromValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.0, 0.0, 1.0)] toValue:[NSValue valueWithCATransform3D:CATransform3DIdentity] timingFunction:[CAMediaTimingFunction functionWithControlPoints:0.0:0.0:0.0:1.0]]; [_innerCircle addAnimation:animation forKey:kSCScaleAnimationKey]; } @end