Skip to content

Commit

Permalink
Support the Slow Animations option of the iOS simulator (#21157)
Browse files Browse the repository at this point in the history
Summary:
RN animations currently ignore the `Slow Animations` option on the iOS simulator because we don't use UIKit animations directly. This uses a private api to get the slow coefficient and use it in the native animated driver. We only compile the private api code on simulator so this won't cause issues for app store approval. One possible issue is that the api changes in new iOS versions but I think it's reasonable to do this.

Note that this won't work with JS driven animations, we could expose the slow coefficient as a constant and use that in JS but I decided not to implement it.
Pull Request resolved: #21157

Differential Revision: D9980306

Pulled By: sahrens

fbshipit-source-id: bdbce2e469261a75cb4b9a251e8e8f212bb9c4e7
  • Loading branch information
janicduplessis authored and facebook-github-bot committed Sep 20, 2018
1 parent a6f47d4 commit 40bcc38
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 14 deletions.
3 changes: 2 additions & 1 deletion Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import <UIKit/UIKit.h>
#import <React/RCTConvert.h>

#import "RCTAnimationUtils.h"
#import "RCTValueAnimatedNode.h"

@interface RCTDecayAnimation ()
Expand Down Expand Up @@ -100,7 +101,7 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime

CGFloat value = _fromValue +
(_velocity / (1 - _deceleration)) *
(1 - exp(-(1 - _deceleration) * (currentTime - _frameStartTime) * 1000.0));
(1 - exp(-(1 - _deceleration) * (currentTime - _frameStartTime) * 1000.0 / RCTAnimationDragCoefficient()));

[self updateValue:value];

Expand Down
2 changes: 1 addition & 1 deletion Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
}

_animationCurrentTime = currentTime;
NSTimeInterval currentDuration = _animationCurrentTime - _animationStartTime;
NSTimeInterval currentDuration = (_animationCurrentTime - _animationStartTime) / RCTAnimationDragCoefficient();

// Determine how many frames have passed since last update.
// Get index of frames that surround the current interval
Expand Down
25 changes: 13 additions & 12 deletions Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>

#import "RCTAnimationUtils.h"
#import "RCTValueAnimatedNode.h"

@interface RCTSpringAnimation ()
Expand Down Expand Up @@ -45,7 +46,7 @@ @implementation RCTSpringAnimation

NSInteger _iterations;
NSInteger _currentLoop;

NSTimeInterval _t; // Current time (startTime + dt)
}

Expand Down Expand Up @@ -110,7 +111,7 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
// Animation has not begun or animation has already finished.
return;
}

// calculate delta time
NSTimeInterval deltaTime;
if(_animationStartTime == -1) {
Expand All @@ -120,22 +121,22 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
} else {
// Handle frame drops, and only advance dt by a max of MAX_DELTA_TIME
deltaTime = MIN(MAX_DELTA_TIME, currentTime - _animationCurrentTime);
_t = _t + deltaTime;
_t = _t + deltaTime / RCTAnimationDragCoefficient();
}

// store the timestamp
_animationCurrentTime = currentTime;

CGFloat c = _damping;
CGFloat m = _mass;
CGFloat k = _stiffness;
CGFloat v0 = -_initialVelocity;

CGFloat zeta = c / (2 * sqrtf(k * m));
CGFloat omega0 = sqrtf(k / m);
CGFloat omega1 = omega0 * sqrtf(1.0 - (zeta * zeta));
CGFloat x0 = _toValue - _fromValue;

CGFloat position;
CGFloat velocity;
if (zeta < 1) {
Expand Down Expand Up @@ -163,12 +164,12 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
velocity =
envelope * (v0 * (_t * omega0 - 1) + _t * x0 * (omega0 * omega0));
}

_lastPosition = position;
_lastVelocity = velocity;

[self onUpdate:position];

// Conditions for stopping the spring animation
BOOL isOvershooting = NO;
if (_overshootClamping && _stiffness != 0) {
Expand All @@ -183,7 +184,7 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
if (_stiffness != 0) {
isDisplacement = ABS(_toValue - position) <= _restDisplacementThreshold;
}

if (isOvershooting || (isVelocity && isDisplacement)) {
if (_stiffness != 0) {
// Ensure that we end up with a round value
Expand All @@ -192,7 +193,7 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
}
[self onUpdate:_toValue];
}

if (_iterations == -1 || _currentLoop < _iterations) {
_lastPosition = _fromValue;
_lastVelocity = _initialVelocity;
Expand Down
6 changes: 6 additions & 0 deletions Libraries/NativeAnimation/RCTAnimationUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ RCT_EXTERN CGFloat RCTInterpolateValue(CGFloat value,

RCT_EXTERN CGFloat RCTRadiansToDegrees(CGFloat radians);
RCT_EXTERN CGFloat RCTDegreesToRadians(CGFloat degrees);

/**
* Coefficient to slow down animations, respects the ios
* simulator `Slow Animations (⌘T)` option.
*/
RCT_EXTERN CGFloat RCTAnimationDragCoefficient(void);
14 changes: 14 additions & 0 deletions Libraries/NativeAnimation/RCTAnimationUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,17 @@ CGFloat RCTDegreesToRadians(CGFloat degrees)
{
return degrees / 180.0 * M_PI;
}

#if TARGET_IPHONE_SIMULATOR
// Based on https://stackoverflow.com/a/13307674
float UIAnimationDragCoefficient(void);
#endif

CGFloat RCTAnimationDragCoefficient()
{
#if TARGET_IPHONE_SIMULATOR
return (CGFloat)UIAnimationDragCoefficient();
#else
return 1.0;
#endif
}

0 comments on commit 40bcc38

Please sign in to comment.