Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
Add fallback mechanism for non-additive animations when beginFromCurr…
Browse files Browse the repository at this point in the history
…entState 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.
  • Loading branch information
jverkoey committed Nov 30, 2017
1 parent efa8937 commit 433eb5f
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 24 deletions.
30 changes: 21 additions & 9 deletions src/MDMMotionAnimator.m
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
12 changes: 7 additions & 5 deletions src/private/CABasicAnimation+MotionAnimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
19 changes: 9 additions & 10 deletions src/private/CABasicAnimation+MotionAnimator.m
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 433eb5f

Please sign in to comment.