From 40bcc38d911d3dbb651c8d2a2a94f4782681cb98 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Thu, 20 Sep 2018 16:05:05 -0700 Subject: [PATCH] Support the `Slow Animations` option of the iOS simulator (#21157) 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: https://github.com/facebook/react-native/pull/21157 Differential Revision: D9980306 Pulled By: sahrens fbshipit-source-id: bdbce2e469261a75cb4b9a251e8e8f212bb9c4e7 --- .../Drivers/RCTDecayAnimation.m | 3 ++- .../Drivers/RCTFrameAnimation.m | 2 +- .../Drivers/RCTSpringAnimation.m | 25 ++++++++++--------- Libraries/NativeAnimation/RCTAnimationUtils.h | 6 +++++ Libraries/NativeAnimation/RCTAnimationUtils.m | 14 +++++++++++ 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m b/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m index 9b3b90e75..27d4edfc3 100644 --- a/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m +++ b/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m @@ -10,6 +10,7 @@ #import #import +#import "RCTAnimationUtils.h" #import "RCTValueAnimatedNode.h" @interface RCTDecayAnimation () @@ -100,7 +101,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) 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]; diff --git a/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m b/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m index c03d4be34..a6dfde500 100644 --- a/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m +++ b/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m @@ -97,7 +97,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } _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 diff --git a/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m index 926e33b37..b7e784b6c 100644 --- a/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m +++ b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m @@ -12,6 +12,7 @@ #import #import +#import "RCTAnimationUtils.h" #import "RCTValueAnimatedNode.h" @interface RCTSpringAnimation () @@ -45,7 +46,7 @@ const NSTimeInterval MAX_DELTA_TIME = 0.064; NSInteger _iterations; NSInteger _currentLoop; - + NSTimeInterval _t; // Current time (startTime + dt) } @@ -110,7 +111,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) // Animation has not begun or animation has already finished. return; } - + // calculate delta time NSTimeInterval deltaTime; if(_animationStartTime == -1) { @@ -120,22 +121,22 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } 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) { @@ -163,12 +164,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) 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) { @@ -183,7 +184,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) if (_stiffness != 0) { isDisplacement = ABS(_toValue - position) <= _restDisplacementThreshold; } - + if (isOvershooting || (isVelocity && isDisplacement)) { if (_stiffness != 0) { // Ensure that we end up with a round value @@ -192,7 +193,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } [self onUpdate:_toValue]; } - + if (_iterations == -1 || _currentLoop < _iterations) { _lastPosition = _fromValue; _lastVelocity = _initialVelocity; diff --git a/Libraries/NativeAnimation/RCTAnimationUtils.h b/Libraries/NativeAnimation/RCTAnimationUtils.h index b5f766a58..39f2fe470 100644 --- a/Libraries/NativeAnimation/RCTAnimationUtils.h +++ b/Libraries/NativeAnimation/RCTAnimationUtils.h @@ -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); diff --git a/Libraries/NativeAnimation/RCTAnimationUtils.m b/Libraries/NativeAnimation/RCTAnimationUtils.m index a7868f3c8..7ffb4690d 100644 --- a/Libraries/NativeAnimation/RCTAnimationUtils.m +++ b/Libraries/NativeAnimation/RCTAnimationUtils.m @@ -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 +}