From 8d145e348299ab612afba21be468e5f692b29087 Mon Sep 17 00:00:00 2001 From: Adam Kirk Date: Wed, 21 May 2014 07:13:48 -0600 Subject: [PATCH] Implemented beginTime instead of dispatch_after to add delay. fixes #19. Also added more convenience methods that include the delay parameter. --- MTAnimation/MTViewController.m | 104 ++++--- MTAnimation/UIView+MTAnimation.h | 12 +- MTAnimation/UIView+MTAnimation.m | 263 ++++++++++-------- .../en.lproj/MainStoryboard_iPad.storyboard | 74 +++-- 4 files changed, 262 insertions(+), 191 deletions(-) diff --git a/MTAnimation/MTViewController.m b/MTAnimation/MTViewController.m index 9cc48b9..f1eb93b 100644 --- a/MTAnimation/MTViewController.m +++ b/MTAnimation/MTViewController.m @@ -17,7 +17,8 @@ @interface MTViewController () @property (weak, nonatomic) IBOutlet UIView *animationAreaView; @property (assign, nonatomic) CGRect startFrame; @property (assign, nonatomic) MTTimingFunction timingFuction; -@property (assign, nonatomic) CGFloat duration; +@property (assign, nonatomic) NSTimeInterval duration; +@property (assign, nonatomic) NSTimeInterval delay; @property (assign, nonatomic) CGFloat exaggeration; @property (assign, nonatomic) CGFloat endY; @property (assign, nonatomic) CGFloat endX; @@ -33,17 +34,18 @@ @implementation MTViewController - (void)viewDidLoad { [super viewDidLoad]; - _startFrame = _logoImageView.frame; - _timingFuction = kMTEaseOutBack; - _duration = 1; - _exaggeration = 1.7; - _endY = 50; - _endX = 50; - _endScale = 1; - _endRotation = 0; - _endAlpha = 1; - - [_animationAreaView addGestureRecognizer:[[UITapGestureRecognizer alloc] + self.startFrame = self.logoImageView.frame; + self.timingFuction = kMTEaseOutBack; + self.duration = 1; + self.delay = 0; + self.exaggeration = 1.7; + self.endY = 50; + self.endX = 50; + self.endScale = 1; + self.endRotation = 0; + self.endAlpha = 1; + + [self.animationAreaView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(animationAreaWasTapped:)]]; } @@ -51,9 +53,9 @@ - (void)viewDidLoad - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - [_tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:25 inSection:0] - animated:NO - scrollPosition:UITableViewScrollPositionTop]; + [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:25 inSection:0] + animated:NO + scrollPosition:UITableViewScrollPositionTop]; } @@ -63,58 +65,51 @@ - (void)viewDidAppear:(BOOL)animated - (void)animationAreaWasTapped:(UITapGestureRecognizer *)gesture { - CGPoint point = [gesture locationInView:_animationAreaView]; - _endX = point.x - (_logoImageView.frame.size.width / 2.0); - _endY = point.y - (_logoImageView.frame.size.height / 2.0); + CGPoint point = [gesture locationInView:self.animationAreaView]; + self.endX = point.x - (self.logoImageView.frame.size.width / 2.0); + self.endY = point.y - (self.logoImageView.frame.size.height / 2.0); [self animate]; } - (IBAction)durationChanged:(id)sender { UISlider *slider = (UISlider *)sender; - _duration = slider.value; + self.duration = slider.value; [self animate]; } -- (IBAction)exaggerationChanged:(id)sender +- (IBAction)delayChanged:(id)sender { UISlider *slider = (UISlider *)sender; - _exaggeration = slider.value; + self.delay = slider.value; [self animate]; } -- (IBAction)endXChanged:(id)sender -{ - UISlider *slider = (UISlider *)sender; - _endX = slider.value; - [self animate]; -} - -- (IBAction)endYChanged:(id)sender +- (IBAction)exaggerationChanged:(id)sender { - UISlider *slider = (UISlider *)sender; - _endY = slider.value; - [self animate]; + UISlider *slider = (UISlider *)sender; + self.exaggeration = slider.value; + [self animate]; } - (IBAction)endScaleChanged:(id)sender { UISlider *slider = (UISlider *)sender; - _endScale = slider.value; + self.endScale = slider.value; [self animate]; } - (IBAction)endRotationChanged:(id)sender { UISlider *slider = (UISlider *)sender; - _endRotation = slider.value; + self.endRotation = slider.value; [self animate]; } - (IBAction)endAlphaDidChange:(id)sender { UISlider *slider = (UISlider *)sender; - _endAlpha = slider.value; + self.endAlpha = slider.value; [self animate]; } @@ -158,25 +153,26 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath - (void)animate { - CGRect r = _logoImageView.frame; - r.size = _startFrame.size; - _logoImageView.frame = r; + CGRect r = self.logoImageView.frame; + r.size = self.startFrame.size; + self.logoImageView.frame = r; - _logoImageView.layer.transform = CATransform3DIdentity; - _logoImageView.alpha = 1; + self.logoImageView.layer.transform = CATransform3DIdentity; + self.logoImageView.alpha = 1; - [UIView mt_animateWithDuration:_duration - timingFunction:_timingFuction + [UIView mt_animateWithDuration:self.duration + delay:self.delay + timingFunction:self.timingFuction options:MTViewAnimationOptionBeginFromCurrentState animations:^{ - _logoImageView.mt_animationPerspective = -1.0 / 500.0; - CGRect r = _logoImageView.frame; - r.origin.x = _endX; - r.origin.y = _endY; - _logoImageView.frame = [self scaledRect:r]; - _logoImageView.alpha = _endAlpha; - CGFloat radians = mt_degreesToRadians(_endRotation); - _logoImageView.layer.transform = CATransform3DMakeRotation(radians, 0, 1, 0); + self.logoImageView.mt_animationPerspective = -1.0 / 500.0; + CGRect r = self.logoImageView.frame; + r.origin.x = self.endX; + r.origin.y = self.endY; + self.logoImageView.frame = [self scaledRect:r]; + self.logoImageView.alpha = self.endAlpha; + CGFloat radians = mt_degreesToRadians(self.endRotation); + self.logoImageView.layer.transform = CATransform3DMakeRotation(radians, 0, 1, 0); } completion:^{ NSLog(@"completed"); }]; @@ -245,10 +241,10 @@ - (void)viewDidUnload { - (CGRect)scaledRect:(CGRect)r { - CGFloat h = _startFrame.size.height; - CGFloat w = _startFrame.size.width; - CGFloat hh = h * _endScale; - CGFloat ww = w * _endScale; + CGFloat h = self.startFrame.size.height; + CGFloat w = self.startFrame.size.width; + CGFloat hh = h * self.endScale; + CGFloat ww = w * self.endScale; r.size.height = hh; r.size.width = ww; r.origin.y -= (hh - h) / 2.0; diff --git a/MTAnimation/UIView+MTAnimation.h b/MTAnimation/UIView+MTAnimation.h index 9b1d299..f59c4e8 100644 --- a/MTAnimation/UIView+MTAnimation.h +++ b/MTAnimation/UIView+MTAnimation.h @@ -57,6 +57,16 @@ animations:(MTAnimationsBlock)animations completion:(MTAnimationCompletionBlock)completion; +/** + Convenience method. See full method below for param explanations. + */ ++ (void)mt_animateWithDuration:(NSTimeInterval)duration + delay:(NSTimeInterval)delay + timingFunction:(MTTimingFunction)timingFunction + options:(MTViewAnimationOptions)options + animations:(MTAnimationsBlock)animations + completion:(MTAnimationCompletionBlock)completion; + /** Convenience method. See full method below for param explanations. */ @@ -67,8 +77,8 @@ completion:(MTAnimationCompletionBlock)completion; /** - @param views The list of views you will be modifying in the animation block. You must provide all views you'll be modifying. @param duration The duration of the animation. + @param delay A delay before the animation begins. @param timingFunction The timing function to use for the easing. @param options Some of the UIView MTViewAnimationOptions options are implemented. Not all of them yet, but I'm working on it. @param animations Make your changes to your views in this block and they will be animated to those final values. diff --git a/MTAnimation/UIView+MTAnimation.m b/MTAnimation/UIView+MTAnimation.m index fc0a387..34a9700 100644 --- a/MTAnimation/UIView+MTAnimation.m +++ b/MTAnimation/UIView+MTAnimation.m @@ -39,11 +39,6 @@ @interface MTView () @implementation MTView (MTAnimation) -+ (void)load -{ - -} - + (void)mt_animateWithDuration:(NSTimeInterval)duration timingFunction:(MTTimingFunction)timingFunction animations:(MTAnimationsBlock)animations @@ -96,6 +91,22 @@ + (void)mt_animateWithDuration:(NSTimeInterval)duration completion:completion]; } ++ (void)mt_animateWithDuration:(NSTimeInterval)duration + delay:(NSTimeInterval)delay + timingFunction:(MTTimingFunction)timingFunction + options:(MTViewAnimationOptions)options + animations:(MTAnimationsBlock)animations + completion:(MTAnimationCompletionBlock)completion +{ + return [self mt_animateWithDuration:duration + delay:delay + timingFunction:timingFunction + range:MTAnimationRangeFull + options:options + animations:animations + completion:completion]; +} + + (void)mt_animateWithDuration:(NSTimeInterval)duration timingFunction:(MTTimingFunction)timingFunction range:(MTAnimationRange)range @@ -119,123 +130,135 @@ + (void)mt_animateWithDuration:(NSTimeInterval)duration animations:(MTAnimationsBlock)animations completion:(MTAnimationCompletionBlock)completion { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - assert(animations != nil); - assert(range.start >= 0); - assert(range.end <= 1); + assert(animations != nil); + assert(range.start >= 0); + assert(range.end <= 1); - if (duration <= 0) { - if (animations) animations(); - if (completion) completion(); - return; - } + if (duration <= 0) { + if (animations) animations(); + if (completion) completion(); + return; + } - NSArray *views = [self allViewsInWindow]; + CGFloat beginTime = 0; + if (delay > 0) { + beginTime = CACurrentMediaTime() + delay; + } - [CATransaction lock]; - [CATransaction begin]; - [CATransaction setAnimationDuration:duration]; - [CATransaction setCompletionBlock:completion]; - [CATransaction setDisableActions:YES]; + [CATransaction lock]; + [CATransaction begin]; + [CATransaction setAnimationDuration:duration]; + [CATransaction setCompletionBlock:completion]; + [CATransaction setDisableActions:YES]; - for (MTView *view in views) { - [view takeStartSnapshot:options]; - } + NSArray *views = [self allViewsInWindow]; - if (animations) animations(); + for (MTView *view in views) { + [view takeStartSnapshot:options]; + } - NSMutableArray *changedViews = [NSMutableArray new]; - for (MTView *view in views) { + if (animations) animations(); - // apply MTViewAnimationOptionBeginFromCurrentState option - CALayer *current = nil; - if (mt_isInMask(options, MTViewAnimationOptionBeginFromCurrentState)) { - BOOL currentlyAnimating = [[view.layer animationKeys] count] > 0; - if (currentlyAnimating) { - current = view.layer.presentationLayer; - } - } + NSMutableArray *changedViews = [NSMutableArray new]; + for (MTView *view in views) { - if (!CGRectEqualToRect(view.startBounds, view.bounds)) { - [changedViews addObject:view]; - CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; - keyframeAnimation.keyPath = @"bounds"; - keyframeAnimation.duration = duration; - keyframeAnimation.calculationMode = kCAAnimationLinear; - keyframeAnimation.values = [self rectValuesWithDuration:duration - function:timingFunction - from:current ? current.bounds : view.startBounds - to:view.bounds - exaggeration:view.mt_animationExaggeration]; - [view addAnimation:keyframeAnimation - forKey:@"bounds" - range:range - options:options - perspective:view.mt_animationPerspective]; + // apply MTViewAnimationOptionBeginFromCurrentState option + CALayer *current = nil; + if (mt_isInMask(options, MTViewAnimationOptionBeginFromCurrentState)) { + BOOL currentlyAnimating = [[view.layer animationKeys] count] > 0; + if (currentlyAnimating) { + current = view.layer.presentationLayer; } + } + if (!CGRectEqualToRect(view.startBounds, view.bounds)) { + [changedViews addObject:view]; + CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; + keyframeAnimation.beginTime = beginTime; + keyframeAnimation.keyPath = @"bounds"; + keyframeAnimation.duration = duration; + keyframeAnimation.calculationMode = kCAAnimationLinear; + keyframeAnimation.values = [self rectValuesWithDuration:duration + function:timingFunction + from:current ? current.bounds : view.startBounds + to:view.bounds + exaggeration:view.mt_animationExaggeration]; + [view addAnimation:keyframeAnimation + delay:delay + forKey:@"bounds" + range:range + options:options + perspective:view.mt_animationPerspective]; + } - if (!CGPointEqualToPoint(view.startCenter, view.center)) { - [changedViews addObject:view]; - CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; - keyframeAnimation.keyPath = @"position"; - keyframeAnimation.duration = duration; - keyframeAnimation.calculationMode = kCAAnimationLinear; - keyframeAnimation.values = [self pointValuesWithDuration:duration - function:timingFunction - from:current ? current.position : view.startCenter - to:view.center - exaggeration:view.mt_animationExaggeration]; - [view addAnimation:keyframeAnimation - forKey:@"position" - range:range - options:options - perspective:view.mt_animationPerspective]; - } - if (!CATransform3DEqualToTransform(view.startTransform3D, view.layer.transform)) { - [changedViews addObject:view]; - CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; - keyframeAnimation.keyPath = @"transform"; - keyframeAnimation.duration = duration; - keyframeAnimation.calculationMode = kCAAnimationLinear; - keyframeAnimation.values = [self transformValuesWithDuration:duration - function:timingFunction - from:current ? current.transform : view.startTransform3D - to:view.layer.transform - exaggeration:view.mt_animationExaggeration]; - [view addAnimation:keyframeAnimation - forKey:@"transform" - range:range - options:options - perspective:view.mt_animationPerspective]; - } + if (!CGPointEqualToPoint(view.startCenter, view.center)) { + [changedViews addObject:view]; + CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; + keyframeAnimation.beginTime = beginTime; + keyframeAnimation.keyPath = @"position"; + keyframeAnimation.duration = duration; + keyframeAnimation.calculationMode = kCAAnimationLinear; + keyframeAnimation.values = [self pointValuesWithDuration:duration + function:timingFunction + from:current ? current.position : view.startCenter + to:view.center + exaggeration:view.mt_animationExaggeration]; + [view addAnimation:keyframeAnimation + delay:delay + forKey:@"position" + range:range + options:options + perspective:view.mt_animationPerspective]; + } - if (view.startAlpha != view.mt_alpha) { - [changedViews addObject:view]; - CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; - keyframeAnimation.keyPath = @"opacity"; - keyframeAnimation.duration = duration; - keyframeAnimation.calculationMode = kCAAnimationLinear; - keyframeAnimation.values = [self floatValuesWithDuration:duration + if (!CATransform3DEqualToTransform(view.startTransform3D, view.layer.transform)) { + [changedViews addObject:view]; + CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; + keyframeAnimation.beginTime = beginTime; + keyframeAnimation.keyPath = @"transform"; + keyframeAnimation.duration = duration; + keyframeAnimation.calculationMode = kCAAnimationLinear; + keyframeAnimation.values = [self transformValuesWithDuration:duration function:timingFunction - from:current ? current.opacity : view.startAlpha - to:view.mt_alpha + from:current ? current.transform : view.startTransform3D + to:view.layer.transform exaggeration:view.mt_animationExaggeration]; - [view addAnimation:keyframeAnimation - forKey:@"opacity" - range:range - options:options - perspective:view.mt_animationPerspective]; - } + [view addAnimation:keyframeAnimation + delay:delay + forKey:@"transform" + range:range + options:options + perspective:view.mt_animationPerspective]; } - - for (MTView *view in changedViews) { - [view.layer layoutIfNeeded]; + + if (view.startAlpha != view.mt_alpha) { + [changedViews addObject:view]; + CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation new]; + keyframeAnimation.beginTime = beginTime; + keyframeAnimation.keyPath = @"opacity"; + keyframeAnimation.duration = duration; + keyframeAnimation.calculationMode = kCAAnimationLinear; + keyframeAnimation.values = [self floatValuesWithDuration:duration + function:timingFunction + from:current ? current.opacity : view.startAlpha + to:view.mt_alpha + exaggeration:view.mt_animationExaggeration]; + [view addAnimation:keyframeAnimation + delay:delay + forKey:@"opacity" + range:range + options:options + perspective:view.mt_animationPerspective]; } - [CATransaction commit]; - [CATransaction unlock]; - }); + } + + for (MTView *view in changedViews) { + [view.layer layoutIfNeeded]; + } + + [CATransaction commit]; + [CATransaction unlock]; } @@ -371,6 +394,7 @@ + (NSArray *)floatValuesWithDuration:(NSTimeInterval)duration } - (void)addAnimation:(CAKeyframeAnimation *)animation + delay:(NSTimeInterval)delay forKey:(NSString *)key range:(MTAnimationRange)range options:(MTViewAnimationOptions)options @@ -425,27 +449,46 @@ - (void)addAnimation:(CAKeyframeAnimation *)animation perspectiveTransform.m34 = perspective; self.layer.superlayer.sublayerTransform = perspectiveTransform; + void (^setFinalValueBlock)() = nil; + // add the animation if ([key isEqualToString:@"bounds"]) { self.bounds = self.startBounds; [self.layer addAnimation:animation forKey:key]; - self.layer.bounds = [[animation.values lastObject] MTRectValue]; + setFinalValueBlock = ^{ + self.layer.bounds = [[animation.values lastObject] MTRectValue]; + }; } else if ([key isEqualToString:@"position"]) { self.center = self.startCenter; [self.layer addAnimation:animation forKey:key]; - self.layer.position = [[animation.values lastObject] MTPointValue]; - self.center = self.layer.position; + setFinalValueBlock = ^{ + self.layer.position = [[animation.values lastObject] MTPointValue]; + self.center = self.layer.position; + }; } else if ([key isEqualToString:@"opacity"]) { self.mt_alpha = self.startAlpha; [self.layer addAnimation:animation forKey:key]; - self.layer.opacity = [[animation.values lastObject] floatValue]; + setFinalValueBlock = ^{ + self.layer.opacity = [[animation.values lastObject] floatValue]; + }; } else if ([key isEqualToString:@"transform"]) { self.layer.transform = self.startTransform3D; [self.layer addAnimation:animation forKey:key]; - self.layer.transform = [[animation.values lastObject] CATransform3DValue]; + setFinalValueBlock = ^{ + self.layer.transform = [[animation.values lastObject] CATransform3DValue]; + }; + } + + if (delay > 0) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + if (setFinalValueBlock) setFinalValueBlock(); + }); + } + else { + if (setFinalValueBlock) setFinalValueBlock(); } } diff --git a/MTAnimation/en.lproj/MainStoryboard_iPad.storyboard b/MTAnimation/en.lproj/MainStoryboard_iPad.storyboard index 361b852..b7e6f67 100644 --- a/MTAnimation/en.lproj/MainStoryboard_iPad.storyboard +++ b/MTAnimation/en.lproj/MainStoryboard_iPad.storyboard @@ -1,5 +1,5 @@ - + @@ -18,11 +18,10 @@ - + - @@ -57,7 +56,7 @@ - + @@ -65,14 +64,14 @@ - + @@ -80,14 +79,14 @@ - + @@ -95,14 +94,14 @@ - + @@ -110,14 +109,14 @@ - + @@ -125,7 +124,7 @@ - + @@ -143,43 +142,66 @@ + + + + + + + + + + + - - + + + + + - + - + + - + + - + + - - + + - - + + + - - + - +