Permalink
Browse files

Add support for springs for NativeAnimated on iOS

Summary:
This diff adds support for native spring animations on iOS. This overlaps some spring work done by kmagiera on the Android side of things.

**Test plan (required)**

Run UIExplorer NativeAnimated examples before and after - compare the results. Pay special attention to the spring examples.
Closes #9048

Differential Revision: D4056088

Pulled By: foghina

fbshipit-source-id: a593408cb61cb850572bab4a0884f7157cece656
  • Loading branch information...
ryangomba authored and Facebook Github Bot committed Oct 21, 2016
1 parent 5e94114 commit d950db4ef76ecbf229ea2de1c09e5616f2a4aa24
@@ -14,20 +14,17 @@
NS_ASSUME_NONNULL_BEGIN
-@interface RCTAnimationDriverNode : NSObject
+@protocol RCTAnimationDriver <NSObject>
@property (nonatomic, readonly) NSNumber *animationId;
-@property (nonatomic, readonly) NSNumber *outputValue;
-
+@property (nonatomic, readonly) RCTValueAnimatedNode *valueNode;
@property (nonatomic, readonly) BOOL animationHasBegun;
@property (nonatomic, readonly) BOOL animationHasFinished;
- (instancetype)initWithId:(NSNumber *)animationId
- delay:(NSTimeInterval)delay
- toValue:(CGFloat)toValue
- frames:(NSArray<NSNumber *> *)frames
+ config:(NSDictionary *)config
forNode:(RCTValueAnimatedNode *)valueNode
- callBack:(nullable RCTResponseSenderBlock)callback NS_DESIGNATED_INITIALIZER;
+ callBack:(nullable RCTResponseSenderBlock)callback;
- (void)startAnimation;
- (void)stopAnimation;
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "RCTAnimationDriver.h"
+
+@interface RCTFrameAnimation : NSObject<RCTAnimationDriver>
+
+@end
@@ -7,43 +7,53 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
-#import "RCTAnimationDriverNode.h"
+#import "RCTFrameAnimation.h"
#import <UIKit/UIKit.h>
+#import "RCTConvert.h"
#import "RCTAnimationUtils.h"
#import "RCTDefines.h"
#import "RCTValueAnimatedNode.h"
const double SINGLE_FRAME_INTERVAL = 1.0 / 60.0;
-@implementation RCTAnimationDriverNode
+@interface RCTFrameAnimation ()
+
+@property (nonatomic, strong) NSNumber *animationId;
+@property (nonatomic, strong) RCTValueAnimatedNode *valueNode;
+@property (nonatomic, assign) BOOL animationHasBegun;
+@property (nonatomic, assign) BOOL animationHasFinished;
+
+@end
+
+@implementation RCTFrameAnimation
{
NSArray<NSNumber *> *_frames;
CGFloat _toValue;
CGFloat _fromValue;
NSTimeInterval _delay;
NSTimeInterval _animationStartTime;
NSTimeInterval _animationCurrentTime;
- RCTValueAnimatedNode *_valueNode;
RCTResponseSenderBlock _callback;
}
-- (instancetype)initWithId:(nonnull NSNumber *)animationId
- delay:(NSTimeInterval)delay
- toValue:(CGFloat)toValue
- frames:(nonnull NSArray<NSNumber *> *)frames
- forNode:(nonnull RCTValueAnimatedNode *)valueNode
- callBack:(nullable RCTResponseSenderBlock)callback
+- (instancetype)initWithId:(NSNumber *)animationId
+ config:(NSDictionary *)config
+ forNode:(RCTValueAnimatedNode *)valueNode
+ callBack:(nullable RCTResponseSenderBlock)callback;
{
if ((self = [super init])) {
+ NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1;
+ NSTimeInterval delay = [RCTConvert double:config[@"delay"]];
+ NSArray<NSNumber *> *frames = [RCTConvert NSNumberArray:config[@"frames"]];
+
_animationId = animationId;
- _toValue = toValue;
+ _toValue = toValue.floatValue;
_fromValue = valueNode.value;
_valueNode = valueNode;
_delay = delay;
_frames = [frames copy];
- _outputValue = @0;
_callback = [callback copy];
}
return self;
@@ -135,7 +145,6 @@ - (void)updateOutputWithFrameOutput:(CGFloat)frameOutput
EXTRAPOLATE_TYPE_EXTEND,
EXTRAPOLATE_TYPE_EXTEND);
- _outputValue = @(outputValue);
_valueNode.value = outputValue;
[_valueNode setNeedsUpdate];
}
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "RCTAnimationDriver.h"
+
+@interface RCTSpringAnimation : NSObject<RCTAnimationDriver>
+
+@end
@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "RCTSpringAnimation.h"
+
+#import <UIKit/UIKit.h>
+
+#import "RCTConvert.h"
+#import "RCTAnimationUtils.h"
+#import "RCTDefines.h"
+#import "RCTValueAnimatedNode.h"
+
+@interface RCTSpringAnimation ()
+
+@property (nonatomic, strong) NSNumber *animationId;
+@property (nonatomic, strong) RCTValueAnimatedNode *valueNode;
+@property (nonatomic, assign) BOOL animationHasBegun;
+@property (nonatomic, assign) BOOL animationHasFinished;
+
+@end
+
+@implementation RCTSpringAnimation
+{
+ CGFloat _toValue;
+ CGFloat _fromValue;
+ BOOL _overshootClamping;
+ CGFloat _restDisplacementThreshold;
+ CGFloat _restSpeedThreshold;
+ CGFloat _tension;
+ CGFloat _friction;
+ CGFloat _initialVelocity;
+ NSTimeInterval _animationStartTime;
+ NSTimeInterval _animationCurrentTime;
+ RCTResponseSenderBlock _callback;
+
+ CGFloat _lastPosition;
+ CGFloat _lastVelocity;
+}
+
+- (instancetype)initWithId:(NSNumber *)animationId
+ config:(NSDictionary *)config
+ forNode:(RCTValueAnimatedNode *)valueNode
+ callBack:(nullable RCTResponseSenderBlock)callback
+{
+ if ((self = [super init])) {
+ _animationId = animationId;
+ _toValue = [RCTConvert CGFloat:config[@"toValue"]];
+ _fromValue = valueNode.value;
+ _valueNode = valueNode;
+ _overshootClamping = [RCTConvert BOOL:config[@"overshootClamping"]];
+ _restDisplacementThreshold = [RCTConvert CGFloat:config[@"restDisplacementThreshold"]];
+ _restSpeedThreshold = [RCTConvert CGFloat:config[@"restSpeedThreshold"]];
+ _tension = [RCTConvert CGFloat:config[@"tension"]];
+ _friction = [RCTConvert CGFloat:config[@"friction"]];
+ _initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
+ _callback = [callback copy];
+
+ _lastPosition = _fromValue;
+ _lastVelocity = _initialVelocity;
+ }
+ return self;
+}
+
+RCT_NOT_IMPLEMENTED(- (instancetype)init)
+
+- (void)startAnimation
+{
+ _animationStartTime = CACurrentMediaTime();
+ _animationCurrentTime = _animationStartTime;
+ _animationHasBegun = YES;
+}
+
+- (void)stopAnimation
+{
+ _animationHasFinished = YES;
+}
+
+- (void)removeAnimation
+{
+ [self stopAnimation];
+ _valueNode = nil;
+ if (_callback) {
+ _callback(@[@{
+ @"finished": @(_animationHasFinished)
+ }]);
+ }
+}
+
+- (void)stepAnimation
+{
+ if (!_animationHasBegun || _animationHasFinished) {
+ // Animation has not begun or animation has already finished.
+ return;
+ }
+
+ // We are using a fixed time step and a maximum number of iterations.
+ // The following post provides a lot of thoughts into how to build this
+ // loop: http://gafferongames.com/game-physics/fix-your-timestep/
+ CGFloat TIMESTEP_MSEC = 1;
+ // Velocity is based on seconds instead of milliseconds
+ CGFloat step = TIMESTEP_MSEC / 1000;
+
+ NSTimeInterval currentTime = CACurrentMediaTime();
+ NSInteger numSteps = floorf((currentTime - _animationCurrentTime) / step);
+ _animationCurrentTime = currentTime;
+ if (numSteps == 0) {
+ return;
+ }
+
+ CGFloat position = _lastPosition;
+ CGFloat velocity = _lastVelocity;
+
+ CGFloat tempPosition = _lastPosition;
+ CGFloat tempVelocity = _lastVelocity;
+
+ for (NSInteger i = 0; i < numSteps; ++i) {
+ // This is using RK4. A good blog post to understand how it works:
+ // http://gafferongames.com/game-physics/integration-basics/
+ CGFloat aVelocity = velocity;
+ CGFloat aAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity;
+ tempPosition = position + aVelocity * step / 2;
+ tempVelocity = velocity + aAcceleration * step / 2;
+
+ CGFloat bVelocity = tempVelocity;
+ CGFloat bAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity;
+ tempPosition = position + bVelocity * step / 2;
+ tempVelocity = velocity + bAcceleration * step / 2;
+
+ CGFloat cVelocity = tempVelocity;
+ CGFloat cAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity;
+ tempPosition = position + cVelocity * step / 2;
+ tempVelocity = velocity + cAcceleration * step / 2;
+
+ CGFloat dVelocity = tempVelocity;
+ CGFloat dAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity;
+ tempPosition = position + cVelocity * step / 2;
+ tempVelocity = velocity + cAcceleration * step / 2;
+
+ CGFloat dxdt = (aVelocity + 2 * (bVelocity + cVelocity) + dVelocity) / 6;
+ CGFloat dvdt = (aAcceleration + 2 * (bAcceleration + cAcceleration) + dAcceleration) / 6;
+
+ position += dxdt * step;
+ velocity += dvdt * step;
+ }
+
+ _lastPosition = position;
+ _lastVelocity = velocity;
+
+ [self onUpdate:position];
+
+ if (_animationHasFinished) {
+ return;
+ }
+
+ // Conditions for stopping the spring animation
+ BOOL isOvershooting = NO;
+ if (_overshootClamping && _tension != 0) {
+ if (_fromValue < _toValue) {
+ isOvershooting = position > _toValue;
+ } else {
+ isOvershooting = position < _toValue;
+ }
+ }
+ BOOL isVelocity = ABS(velocity) <= _restSpeedThreshold;
+ BOOL isDisplacement = YES;
+ if (_tension != 0) {
+ isDisplacement = ABS(_toValue - position) <= _restDisplacementThreshold;
+ }
+
+ if (isOvershooting || (isVelocity && isDisplacement)) {
+ if (_tension != 0) {
+ // Ensure that we end up with a round value
+ if (_animationHasFinished) {
+ return;
+ }
+ [self onUpdate:_toValue];
+ }
+
+ [self stopAnimation];
+ }
+}
+
+- (void)onUpdate:(CGFloat)outputValue
+{
+ _valueNode.value = outputValue;
+ [_valueNode setNeedsUpdate];
+}
+
+- (void)cleanupAnimationUpdate
+{
+ [_valueNode cleanupAnimationUpdate];
+}
+
+@end
Oops, something went wrong.

0 comments on commit d950db4

Please sign in to comment.