From 433eb5fa683562428c0fa44a38d85e2bd740e6c9 Mon Sep 17 00:00:00 2001 From: featherless Date: Thu, 30 Nov 2017 09:19:10 -0500 Subject: [PATCH] Add fallback mechanism for non-additive animations when beginFromCurrentState is enabled. (#76) When beginFromCurrentState is enabled, the user expects any new animations to animate from the layer's current rendered state. What this actually means in practice depends on whether the new animation is additive or not. If the animation is additive, we always want to read from the model layer because we need to calculate displacement from the old destination to the new destination. If the animation is not additive, we read from the presentation layer if available so that the view begins animating from its current rendered position. The difference between the two approaches is that additive animations will always appear to preserve momentum - potentially even moving in the opposite direction for a brief moment - while non-additive animations will instantly begin moving toward the new destination. --- src/MDMMotionAnimator.m | 30 +++++++++++++------ src/private/CABasicAnimation+MotionAnimator.h | 12 ++++---- src/private/CABasicAnimation+MotionAnimator.m | 19 ++++++------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/MDMMotionAnimator.m b/src/MDMMotionAnimator.m index bf49f5e..d43406c 100644 --- a/src/MDMMotionAnimator.m +++ b/src/MDMMotionAnimator.m @@ -88,27 +88,39 @@ - (void)animateWithTiming:(MDMMotionTiming)timing } animation.keyPath = keyPath; + animation.toValue = [values lastObject]; - id initialValue; - if (_beginFromCurrentState) { - if ([layer presentationLayer]) { - initialValue = [[layer presentationLayer] valueForKeyPath:keyPath]; + animation.additive = self.additive && MDMCanAnimationBeAdditive(keyPath, animation.toValue); + + // Now that we know whether the animation will be additive, we can calculate the from value. + id fromValue; + if (self.beginFromCurrentState) { + // Additive animations always read from the model layer's value so that the new displacement + // reflects the change in destination and momentum appears to be conserved across multiple + // animations. + // + // Non-additive animations should try to read from the presentation layer's current value + // because we'll be interrupting whatever animation previously existed and immediately moving + // toward the new destination. + BOOL wantsPresentationValue = !animation.additive; + + if (wantsPresentationValue && [layer presentationLayer]) { + fromValue = [[layer presentationLayer] valueForKeyPath:keyPath]; } else { - initialValue = [layer valueForKeyPath:keyPath]; + fromValue = [layer valueForKeyPath:keyPath]; } } else { - initialValue = [values firstObject]; + fromValue = [values firstObject]; } - animation.fromValue = initialValue; - animation.toValue = [values lastObject]; + animation.fromValue = fromValue; if ([animation.fromValue isEqual:animation.toValue]) { exitEarly(); return; } - MDMConfigureAnimation(animation, self.additive, timing); + MDMConfigureAnimation(animation, timing); if (timing.delay != 0) { animation.beginTime = ([layer convertTime:CACurrentMediaTime() fromLayer:nil] diff --git a/src/private/CABasicAnimation+MotionAnimator.h b/src/private/CABasicAnimation+MotionAnimator.h index 6adc7ac..5761a75 100644 --- a/src/private/CABasicAnimation+MotionAnimator.h +++ b/src/private/CABasicAnimation+MotionAnimator.h @@ -24,13 +24,15 @@ FOUNDATION_EXPORT CABasicAnimation *MDMAnimationFromTiming(MDMMotionTiming timing, CGFloat timeScaleFactor); -// Attempts to configure the provided animation to be additive and, if the animation is a spring -// animation, will extract the initial velocity from the timing and apply it to the animation. +// Returns a Boolean indicating whether or not an animation with the given key path and toValue +// can be animated additively. +FOUNDATION_EXPORT BOOL MDMCanAnimationBeAdditive(NSString *keyPath, id toValue); + +// If the animation's additive property is enabled, then its from/to values will be transformed into +// additive equivalents. // // Not all animation value types support being additive. If an animation's value type was not // supported, the animation's values will not be modified. // // If the from and to values of the animation match then the behavior is undefined. -FOUNDATION_EXPORT void MDMConfigureAnimation(CABasicAnimation *animation, - BOOL wantsAdditive, - MDMMotionTiming timing); +FOUNDATION_EXPORT void MDMConfigureAnimation(CABasicAnimation *animation, MDMMotionTiming timing); diff --git a/src/private/CABasicAnimation+MotionAnimator.m b/src/private/CABasicAnimation+MotionAnimator.m index ff555e7..56f5602 100644 --- a/src/private/CABasicAnimation+MotionAnimator.m +++ b/src/private/CABasicAnimation+MotionAnimator.m @@ -86,10 +86,12 @@ static BOOL IsCGSizeType(id someValue) { return animation; } -void MDMConfigureAnimation(CABasicAnimation *animation, - BOOL wantsAdditive, - MDMMotionTiming timing) { - if (!wantsAdditive && timing.curve.type != MDMMotionCurveTypeSpring) { +BOOL MDMCanAnimationBeAdditive(NSString *keyPath, id toValue) { + return IsNumberValue(toValue) || IsCGSizeType(toValue) || IsCGPointType(toValue); +} + +void MDMConfigureAnimation(CABasicAnimation *animation, MDMMotionTiming timing) { + if (!animation.additive && timing.curve.type != MDMMotionCurveTypeSpring) { return; // Nothing to do here. } @@ -126,10 +128,9 @@ void MDMConfigureAnimation(CABasicAnimation *animation, CGFloat displacement = to - from; CGFloat additiveDisplacement = -displacement; - if (wantsAdditive) { + if (animation.additive) { animation.fromValue = @(additiveDisplacement); animation.toValue = @0; - animation.additive = true; } #pragma clang diagnostic push @@ -187,10 +188,9 @@ void MDMConfigureAnimation(CABasicAnimation *animation, CGSize to = [animation.toValue CGSizeValue]; CGSize additiveDisplacement = CGSizeMake(from.width - to.width, from.height - to.height); - if (wantsAdditive) { + if (animation.additive) { animation.fromValue = [NSValue valueWithCGSize:additiveDisplacement]; animation.toValue = [NSValue valueWithCGSize:CGSizeZero]; - animation.additive = true; } #pragma clang diagnostic push @@ -219,10 +219,9 @@ void MDMConfigureAnimation(CABasicAnimation *animation, CGPoint to = [animation.toValue CGPointValue]; CGPoint additiveDisplacement = CGPointMake(from.x - to.x, from.y - to.y); - if (wantsAdditive) { + if (animation.additive) { animation.fromValue = [NSValue valueWithCGPoint:additiveDisplacement]; animation.toValue = [NSValue valueWithCGPoint:CGPointZero]; - animation.additive = true; } #pragma clang diagnostic push