Fix NativeAnimation invalidation & races on iOS
Summary: This diff attempts to fix a number of iOS native animation bugs related to improper node invalidation and a race with view creation. The major issues were presented in #9120 as problems 3 and 3b, but I'll recap here: The invalidation model we use is overly complicated and incomplete. The proper combination of `_needsUpdate` and `_hasUpdated` will result in nodes values being recomputed. However, we do not invalidate nodes in all the places we should, e.g. if we create a new view and attach it to an existing value node (see example in #9120). This diff chooses to remove the `_hasUpdated` flag, and simply relies on the `_needsUpdate` flag to mark a node as dirty. We mark nodes as dirty when they are: - created - updated - attached to new parents - detached from old parents - attached to a view Calling `updateNodeIfNecessary` will, if necessary, compute all invalidated parent values before recomputing the node value. It will then apply the update, and mark the no Closes https://github.com/facebook/react-native/pull/10663 Differential Revision: D4120301 Pulled By: mkonicek fbshipit-source-id: e247afcb5d8c15999b8328c664b9f7e764d76a75
This commit is contained in:
parent
bf901d926e
commit
c858420b2d
|
@ -7,6 +7,7 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
@ -31,8 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)stopAnimation;
|
||||
- (void)stepAnimation;
|
||||
- (void)removeAnimation;
|
||||
- (void)cleanupAnimationUpdate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@end
|
||||
|
|
|
@ -150,9 +150,4 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
[_valueNode setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)cleanupAnimationUpdate
|
||||
{
|
||||
[_valueNode cleanupAnimationUpdate];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -193,9 +193,4 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
[_valueNode setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)cleanupAnimationUpdate
|
||||
{
|
||||
[_valueNode cleanupAnimationUpdate];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
@property (nonatomic, copy, readonly) NSDictionary<NSNumber *, RCTAnimatedNode *> *parentNodes;
|
||||
|
||||
@property (nonatomic, readonly) BOOL needsUpdate;
|
||||
@property (nonatomic, readonly) BOOL hasUpdated;
|
||||
|
||||
/**
|
||||
* Marks a node and its children as needing update.
|
||||
|
@ -38,11 +37,6 @@
|
|||
*/
|
||||
- (void)performUpdate NS_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
* Cleans up after a round of updates.
|
||||
*/
|
||||
- (void)cleanupAnimationUpdate NS_REQUIRES_SUPER;
|
||||
|
||||
- (void)addChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
|
||||
- (void)removeChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
|
||||
|
||||
|
|
|
@ -93,30 +93,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
|
||||
- (void)setNeedsUpdate
|
||||
{
|
||||
if (_needsUpdate) {
|
||||
// Has already been marked. Stop branch.
|
||||
return;
|
||||
}
|
||||
_needsUpdate = YES;
|
||||
for (RCTAnimatedNode *child in _childNodes.allValues) {
|
||||
[child setNeedsUpdate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cleanupAnimationUpdate
|
||||
{
|
||||
if (_hasUpdated) {
|
||||
_needsUpdate = NO;
|
||||
_hasUpdated = NO;
|
||||
for (RCTAnimatedNode *child in _childNodes.allValues) {
|
||||
[child cleanupAnimationUpdate];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateNodeIfNecessary
|
||||
{
|
||||
if (_needsUpdate && !_hasUpdated) {
|
||||
if (_needsUpdate) {
|
||||
for (RCTAnimatedNode *parent in _parentNodes.allValues) {
|
||||
[parent updateNodeIfNecessary];
|
||||
}
|
||||
|
@ -126,7 +111,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
|
||||
- (void)performUpdate
|
||||
{
|
||||
_hasUpdated = YES;
|
||||
_needsUpdate = NO;
|
||||
// To be overidden by subclasses
|
||||
// This method is called on a node only if it has been marked for update
|
||||
// during the current update loop
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
#import "RCTAnimatedNode.h"
|
||||
|
||||
@class RCTNativeAnimatedModule;
|
||||
@class RCTUIManager;
|
||||
@class RCTViewPropertyMapper;
|
||||
|
||||
@interface RCTPropsAnimatedNode : RCTAnimatedNode
|
||||
|
||||
@property (nonatomic, readonly) RCTViewPropertyMapper *propertyMapper;
|
||||
|
||||
- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule;
|
||||
- (void)connectToView:(NSNumber *)viewTag uiManager:(RCTUIManager *)uiManager;
|
||||
- (void)disconnectFromView:(NSNumber *)viewTag;
|
||||
|
||||
- (void)performViewUpdatesIfNecessary;
|
||||
|
|
|
@ -8,35 +8,17 @@
|
|||
*/
|
||||
|
||||
#import "RCTPropsAnimatedNode.h"
|
||||
|
||||
#import "RCTAnimationUtils.h"
|
||||
#import "RCTNativeAnimatedModule.h"
|
||||
#import "RCTStyleAnimatedNode.h"
|
||||
#import "RCTValueAnimatedNode.h"
|
||||
#import "RCTViewPropertyMapper.h"
|
||||
|
||||
@implementation RCTPropsAnimatedNode
|
||||
{
|
||||
RCTStyleAnimatedNode *_parentNode;
|
||||
}
|
||||
|
||||
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
|
||||
- (void)connectToView:(NSNumber *)viewTag uiManager:(RCTUIManager *)uiManager
|
||||
{
|
||||
[super onAttachedToNode:parent];
|
||||
if ([parent isKindOfClass:[RCTStyleAnimatedNode class]]) {
|
||||
_parentNode = (RCTStyleAnimatedNode *)parent;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
|
||||
{
|
||||
[super onDetachedFromNode:parent];
|
||||
if (_parentNode == parent) {
|
||||
_parentNode = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule
|
||||
{
|
||||
_propertyMapper = [[RCTViewPropertyMapper alloc] initWithViewTag:viewTag animationModule:animationModule];
|
||||
_propertyMapper = [[RCTViewPropertyMapper alloc] initWithViewTag:viewTag uiManager:uiManager];
|
||||
}
|
||||
|
||||
- (void)disconnectFromView:(NSNumber *)viewTag
|
||||
|
@ -50,11 +32,36 @@
|
|||
[self performViewUpdatesIfNecessary];
|
||||
}
|
||||
|
||||
- (NSString *)propertyNameForParentTag:(NSNumber *)parentTag
|
||||
{
|
||||
__block NSString *propertyName;
|
||||
[self.config[@"props"] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull property, NSNumber * _Nonnull tag, BOOL * _Nonnull stop) {
|
||||
if ([tag isEqualToNumber:parentTag]) {
|
||||
propertyName = property;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
- (void)performViewUpdatesIfNecessary
|
||||
{
|
||||
NSDictionary *updates = [_parentNode updatedPropsDictionary];
|
||||
if (updates.count) {
|
||||
[_propertyMapper updateViewWithDictionary:updates];
|
||||
NSMutableDictionary *props = [NSMutableDictionary dictionary];
|
||||
[self.parentNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull parentTag, RCTAnimatedNode * _Nonnull parentNode, BOOL * _Nonnull stop) {
|
||||
|
||||
if ([parentNode isKindOfClass:[RCTStyleAnimatedNode class]]) {
|
||||
[props addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
|
||||
|
||||
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
NSString *property = [self propertyNameForParentTag:parentTag];
|
||||
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
|
||||
[props setObject:@(value) forKey:property];
|
||||
}
|
||||
|
||||
}];
|
||||
|
||||
if (props.count) {
|
||||
[_propertyMapper updateViewWithDictionary:props];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
|
||||
@interface RCTStyleAnimatedNode : RCTAnimatedNode
|
||||
|
||||
- (NSDictionary<NSString *, NSObject *> *)updatedPropsDictionary;
|
||||
- (NSDictionary<NSString *, NSObject *> *)propsDictionary;
|
||||
|
||||
@end
|
||||
|
|
|
@ -14,21 +14,21 @@
|
|||
|
||||
@implementation RCTStyleAnimatedNode
|
||||
{
|
||||
NSMutableDictionary<NSString *, NSObject *> *_updatedPropsDictionary;
|
||||
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTag:(NSNumber *)tag
|
||||
config:(NSDictionary<NSString *, id> *)config;
|
||||
{
|
||||
if ((self = [super initWithTag:tag config:config])) {
|
||||
_updatedPropsDictionary = [NSMutableDictionary new];
|
||||
_propsDictionary = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)updatedPropsDictionary
|
||||
- (NSDictionary *)propsDictionary
|
||||
{
|
||||
return _updatedPropsDictionary;
|
||||
return _propsDictionary;
|
||||
}
|
||||
|
||||
- (void)performUpdate
|
||||
|
@ -38,22 +38,16 @@
|
|||
NSDictionary<NSString *, NSNumber *> *style = self.config[@"style"];
|
||||
[style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) {
|
||||
RCTAnimatedNode *node = self.parentNodes[nodeTag];
|
||||
if (node && node.hasUpdated) {
|
||||
if (node) {
|
||||
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
|
||||
[self->_updatedPropsDictionary setObject:@(parentNode.value) forKey:property];
|
||||
[self->_propsDictionary setObject:@(parentNode.value) forKey:property];
|
||||
} else if ([node isKindOfClass:[RCTTransformAnimatedNode class]]) {
|
||||
RCTTransformAnimatedNode *parentNode = (RCTTransformAnimatedNode *)node;
|
||||
[self->_updatedPropsDictionary addEntriesFromDictionary:parentNode.updatedPropsDictionary];
|
||||
[self->_propsDictionary addEntriesFromDictionary:parentNode.propsDictionary];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)cleanupAnimationUpdate
|
||||
{
|
||||
[super cleanupAnimationUpdate];
|
||||
[_updatedPropsDictionary removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
|
||||
@interface RCTTransformAnimatedNode : RCTAnimatedNode
|
||||
|
||||
- (NSDictionary<NSString *, NSObject *> *)updatedPropsDictionary;
|
||||
- (NSDictionary<NSString *, NSObject *> *)propsDictionary;
|
||||
|
||||
@end
|
||||
|
|
|
@ -12,21 +12,21 @@
|
|||
|
||||
@implementation RCTTransformAnimatedNode
|
||||
{
|
||||
NSMutableDictionary<NSString *, NSObject *> *_updatedPropsDictionary;
|
||||
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTag:(NSNumber *)tag
|
||||
config:(NSDictionary<NSString *, id> *)config;
|
||||
{
|
||||
if ((self = [super initWithTag:tag config:config])) {
|
||||
_updatedPropsDictionary = [NSMutableDictionary new];
|
||||
_propsDictionary = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)updatedPropsDictionary
|
||||
- (NSDictionary *)propsDictionary
|
||||
{
|
||||
return _updatedPropsDictionary;
|
||||
return _propsDictionary;
|
||||
}
|
||||
|
||||
- (void)performUpdate
|
||||
|
@ -44,7 +44,7 @@
|
|||
if ([type isEqualToString: @"animated"]) {
|
||||
NSNumber *nodeTag = transformConfig[@"nodeTag"];
|
||||
RCTAnimatedNode *node = self.parentNodes[nodeTag];
|
||||
if (!node.hasUpdated || ![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
continue;
|
||||
}
|
||||
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
|
||||
|
@ -82,13 +82,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
_updatedPropsDictionary[@"transform"] = [NSValue valueWithCATransform3D:transform];
|
||||
}
|
||||
|
||||
- (void)cleanupAnimationUpdate
|
||||
{
|
||||
[super cleanupAnimationUpdate];
|
||||
[_updatedPropsDictionary removeAllObjects];
|
||||
_propsDictionary[@"transform"] = [NSValue valueWithCATransform3D:transform];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -38,8 +38,10 @@
|
|||
5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */; };
|
||||
944244D01DB962DA0032A02B /* RCTFrameAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */; };
|
||||
944244D11DB962DC0032A02B /* RCTSpringAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */; };
|
||||
9476E8EC1DC9232D005D5CD1 /* RCTNativeAnimatedNodesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */; };
|
||||
94C129511D40692B0025F25C /* RCTFrameAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */; };
|
||||
94C129521D40692B0025F25C /* RCTSpringAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */; };
|
||||
94DA09181DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */; };
|
||||
94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -79,6 +81,8 @@
|
|||
94C1294D1D4069170025F25C /* RCTFrameAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTFrameAnimation.m; sourceTree = "<group>"; };
|
||||
94C1294E1D4069170025F25C /* RCTSpringAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTSpringAnimation.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
94C1294F1D4069170025F25C /* RCTSpringAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTSpringAnimation.m; sourceTree = "<group>"; };
|
||||
94DA09161DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNativeAnimatedNodesManager.h; sourceTree = "<group>"; };
|
||||
94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNativeAnimatedNodesManager.m; sourceTree = "<group>"; };
|
||||
94DAE3F71D7334A70059942F /* RCTModuloAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTModuloAnimatedNode.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuloAnimatedNode.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -130,6 +134,8 @@
|
|||
13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */,
|
||||
13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */,
|
||||
13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */,
|
||||
94DA09161DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.h */,
|
||||
94DA09171DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m */,
|
||||
94C129491D4069170025F25C /* Drivers */,
|
||||
13E501D51D07A6C9005F35D8 /* Nodes */,
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
|
@ -242,6 +248,7 @@
|
|||
2D3B5EF31D9B0B3400451313 /* RCTViewPropertyMapper.m in Sources */,
|
||||
944244D01DB962DA0032A02B /* RCTFrameAnimation.m in Sources */,
|
||||
944244D11DB962DC0032A02B /* RCTSpringAnimation.m in Sources */,
|
||||
9476E8EC1DC9232D005D5CD1 /* RCTNativeAnimatedNodesManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -251,6 +258,7 @@
|
|||
files = (
|
||||
94C129511D40692B0025F25C /* RCTFrameAnimation.m in Sources */,
|
||||
94C129521D40692B0025F25C /* RCTSpringAnimation.m in Sources */,
|
||||
94DA09181DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.m in Sources */,
|
||||
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */,
|
||||
94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */,
|
||||
193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* 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 <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
|
|
@ -8,134 +8,62 @@
|
|||
*/
|
||||
#import "RCTNativeAnimatedModule.h"
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import "RCTNativeAnimatedNodesManager.h"
|
||||
|
||||
#import "RCTAdditionAnimatedNode.h"
|
||||
#import "RCTAnimationDriver.h"
|
||||
#import "RCTAnimationUtils.h"
|
||||
#import "RCTDiffClampAnimatedNode.h"
|
||||
#import "RCTDivisionAnimatedNode.h"
|
||||
#import "RCTEventAnimation.h"
|
||||
#import "RCTFrameAnimation.h"
|
||||
#import "RCTInterpolationAnimatedNode.h"
|
||||
#import "RCTModuloAnimatedNode.h"
|
||||
#import "RCTMultiplicationAnimatedNode.h"
|
||||
#import "RCTPropsAnimatedNode.h"
|
||||
#import "RCTSpringAnimation.h"
|
||||
#import "RCTStyleAnimatedNode.h"
|
||||
#import "RCTTransformAnimatedNode.h"
|
||||
#import "RCTValueAnimatedNode.h"
|
||||
typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager);
|
||||
|
||||
@implementation RCTNativeAnimatedModule
|
||||
{
|
||||
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
|
||||
NSMutableDictionary<NSNumber *, id<RCTAnimationDriver>> *_animationDrivers;
|
||||
NSMutableDictionary<NSString *, RCTEventAnimation *> *_eventAnimationDrivers;
|
||||
NSMutableSet<id<RCTAnimationDriver>> *_activeAnimations;
|
||||
NSMutableSet<id<RCTAnimationDriver>> *_finishedAnimations;
|
||||
NSMutableSet<RCTValueAnimatedNode *> *_updatedValueNodes;
|
||||
NSMutableSet<RCTPropsAnimatedNode *> *_propAnimationNodes;
|
||||
CADisplayLink *_displayLink;
|
||||
RCTNativeAnimatedNodesManager *_nodesManager;
|
||||
NSMutableArray<AnimatedOperation> *_operations;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
[super setBridge:bridge];
|
||||
|
||||
_animationNodes = [NSMutableDictionary new];
|
||||
_animationDrivers = [NSMutableDictionary new];
|
||||
_eventAnimationDrivers = [NSMutableDictionary new];
|
||||
_activeAnimations = [NSMutableSet new];
|
||||
_finishedAnimations = [NSMutableSet new];
|
||||
_updatedValueNodes = [NSMutableSet new];
|
||||
_propAnimationNodes = [NSMutableSet new];
|
||||
|
||||
[bridge.eventDispatcher addDispatchObserver:self];
|
||||
}
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self.bridge.eventDispatcher removeDispatchObserver:self];
|
||||
}
|
||||
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
return RCTGetUIManagerQueue();
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
return @[@"onAnimatedValueUpdate"];
|
||||
[super setBridge:bridge];
|
||||
|
||||
_nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithUIManager:self.bridge.uiManager];
|
||||
_operations = [NSMutableArray new];
|
||||
|
||||
[bridge.eventDispatcher addDispatchObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark -- API
|
||||
|
||||
RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag
|
||||
config:(NSDictionary<NSString *, id> *)config)
|
||||
{
|
||||
static NSDictionary *map;
|
||||
static dispatch_once_t mapToken;
|
||||
dispatch_once(&mapToken, ^{
|
||||
map = @{@"style" : [RCTStyleAnimatedNode class],
|
||||
@"value" : [RCTValueAnimatedNode class],
|
||||
@"props" : [RCTPropsAnimatedNode class],
|
||||
@"interpolation" : [RCTInterpolationAnimatedNode class],
|
||||
@"addition" : [RCTAdditionAnimatedNode class],
|
||||
@"diffclamp": [RCTDiffClampAnimatedNode class],
|
||||
@"division" : [RCTDivisionAnimatedNode class],
|
||||
@"multiplication" : [RCTMultiplicationAnimatedNode class],
|
||||
@"modulus" : [RCTModuloAnimatedNode class],
|
||||
@"transform" : [RCTTransformAnimatedNode class]};
|
||||
});
|
||||
|
||||
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
|
||||
|
||||
Class nodeClass = map[nodeType];
|
||||
if (!nodeClass) {
|
||||
RCTLogError(@"Animated node type %@ not supported natively", nodeType);
|
||||
return;
|
||||
}
|
||||
|
||||
RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config];
|
||||
|
||||
_animationNodes[tag] = node;
|
||||
|
||||
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[_propAnimationNodes addObject:(RCTPropsAnimatedNode *)node];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager createAnimatedNode:tag config:config];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(connectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag)
|
||||
{
|
||||
RCTAssertParam(parentTag);
|
||||
RCTAssertParam(childTag);
|
||||
|
||||
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
|
||||
RCTAnimatedNode *childNode = _animationNodes[childTag];
|
||||
|
||||
RCTAssertParam(parentNode);
|
||||
RCTAssertParam(childNode);
|
||||
|
||||
[parentNode addChild:childNode];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager connectAnimatedNodes:parentTag childTag:childTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag)
|
||||
{
|
||||
RCTAssertParam(parentTag);
|
||||
RCTAssertParam(childTag);
|
||||
|
||||
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
|
||||
RCTAnimatedNode *childNode = _animationNodes[childTag];
|
||||
|
||||
RCTAssertParam(parentNode);
|
||||
RCTAssertParam(childNode);
|
||||
|
||||
[parentNode removeChild:childNode];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager disconnectAnimatedNodes:parentTag childTag:childTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
|
||||
|
@ -143,178 +71,124 @@ RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
|
|||
config:(NSDictionary<NSString *, id> *)config
|
||||
endCallback:(RCTResponseSenderBlock)callBack)
|
||||
{
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag];
|
||||
|
||||
NSString *type = config[@"type"];
|
||||
id<RCTAnimationDriver> animationDriver;
|
||||
|
||||
if ([type isEqual:@"frames"]) {
|
||||
animationDriver = [[RCTFrameAnimation alloc] initWithId:animationId
|
||||
config:config
|
||||
forNode:valueNode
|
||||
callBack:callBack];
|
||||
|
||||
} else if ([type isEqual:@"spring"]) {
|
||||
animationDriver = [[RCTSpringAnimation alloc] initWithId:animationId
|
||||
config:config
|
||||
forNode:valueNode
|
||||
callBack:callBack];
|
||||
|
||||
} else {
|
||||
RCTLogError(@"Unsupported animation type: %@", config[@"type"]);
|
||||
return;
|
||||
}
|
||||
|
||||
[_activeAnimations addObject:animationDriver];
|
||||
_animationDrivers[animationId] = animationDriver;
|
||||
[animationDriver startAnimation];
|
||||
[self startAnimationLoopIfNeeded];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager startAnimatingNode:animationId nodeTag:nodeTag config:config endCallback:callBack];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
|
||||
{
|
||||
id<RCTAnimationDriver> driver = _animationDrivers[animationId];
|
||||
if (driver) {
|
||||
[driver removeAnimation];
|
||||
[_animationDrivers removeObjectForKey:animationId];
|
||||
[_activeAnimations removeObject:driver];
|
||||
[_finishedAnimations removeObject:driver];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager stopAnimation:animationId];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
|
||||
value:(nonnull NSNumber *)value)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
valueNode.value = value.floatValue;
|
||||
[valueNode setNeedsUpdate];
|
||||
|
||||
[self updateViewsProps];
|
||||
|
||||
[valueNode cleanupAnimationUpdate];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager setAnimatedNodeValue:nodeTag value:value];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
|
||||
offset:(nonnull NSNumber *)offset)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode setOffset:offset.floatValue];
|
||||
[_updatedValueNodes addObject:valueNode];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager setAnimatedNodeOffset:nodeTag offset:offset];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode flattenOffset];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager flattenAnimatedNodeOffset:nodeTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode extractOffset];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager extractAnimatedNodeOffset:nodeTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[(RCTPropsAnimatedNode *)node connectToView:viewTag animatedModule:self];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (viewTag && node && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager disconnectAnimatedNodeFromView:nodeTag viewTag:viewTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node) {
|
||||
[node detachNode];
|
||||
[_animationNodes removeObjectForKey:tag];
|
||||
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
[_updatedValueNodes removeObject:(RCTValueAnimatedNode *)node];
|
||||
} else if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[_propAnimationNodes removeObject:(RCTPropsAnimatedNode *)node];
|
||||
}
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager dropAnimatedNode:tag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
((RCTValueAnimatedNode *)node).valueObserver = self;
|
||||
}
|
||||
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager startListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
((RCTValueAnimatedNode *)node).valueObserver = nil;
|
||||
}
|
||||
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager stopListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(addAnimatedEventToView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName
|
||||
eventMapping:(NSDictionary<NSString *, id> *)eventMapping)
|
||||
{
|
||||
NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]];
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
|
||||
if (!node) {
|
||||
RCTLogError(@"Animated node with tag %@ does not exists", nodeTag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Animated node connected to event should be of type RCTValueAnimatedNode");
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<NSString *> *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]];
|
||||
|
||||
RCTEventAnimation *driver =
|
||||
[[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node];
|
||||
|
||||
_eventAnimationDrivers[[NSString stringWithFormat:@"%@%@", viewTag, eventName]] = driver;
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager addAnimatedEventToView:viewTag eventName:eventName eventMapping:eventMapping];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName)
|
||||
{
|
||||
[_eventAnimationDrivers removeObjectForKey:[NSString stringWithFormat:@"%@%@", viewTag, eventName]];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager removeAnimatedEventFromView:viewTag eventName:eventName];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark -- Batch handling
|
||||
|
||||
- (void)batchDidComplete
|
||||
{
|
||||
NSArray *operations = _operations;
|
||||
_operations = [NSMutableArray new];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[operations enumerateObjectsUsingBlock:^(AnimatedOperation operation, NSUInteger i, BOOL *stop) {
|
||||
operation(self->_nodesManager);
|
||||
}];
|
||||
[self->_nodesManager updateAnimations];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark -- Events
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents
|
||||
{
|
||||
return @[@"onAnimatedValueUpdate"];
|
||||
}
|
||||
|
||||
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value
|
||||
|
@ -326,81 +200,10 @@ RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
|
|||
- (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
|
||||
{
|
||||
// Native animated events only work for events dispatched from the main queue.
|
||||
if (!RCTIsMainQueue() || _eventAnimationDrivers.count == 0) {
|
||||
if (!RCTIsMainQueue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName];
|
||||
RCTEventAnimation *driver = _eventAnimationDrivers[key];
|
||||
|
||||
if (driver) {
|
||||
[driver updateWithEvent:event];
|
||||
[self updateViewsProps];
|
||||
[driver.valueNode cleanupAnimationUpdate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateViewsProps
|
||||
{
|
||||
for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) {
|
||||
[propsNode updateNodeIfNecessary];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Animation Loop
|
||||
|
||||
- (void)startAnimationLoopIfNeeded
|
||||
{
|
||||
if (!_displayLink && (_activeAnimations.count > 0 || _updatedValueNodes.count > 0)) {
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimations)];
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAnimationLoopIfNeeded
|
||||
{
|
||||
if (_displayLink && _activeAnimations.count == 0 && _updatedValueNodes.count == 0) {
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateAnimations
|
||||
{
|
||||
// Step Current active animations
|
||||
// This also recursively marks children nodes as needing update
|
||||
for (id<RCTAnimationDriver> animationDriver in _activeAnimations) {
|
||||
[animationDriver stepAnimation];
|
||||
}
|
||||
|
||||
// Perform node updates for marked nodes.
|
||||
// At this point all nodes that are in need of an update are properly marked as such.
|
||||
for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) {
|
||||
[propsNode updateNodeIfNecessary];
|
||||
}
|
||||
|
||||
// Cleanup nodes and prepare for next cycle. Remove updated nodes from bucket.
|
||||
for (id<RCTAnimationDriver> driverNode in _activeAnimations) {
|
||||
[driverNode cleanupAnimationUpdate];
|
||||
}
|
||||
for (RCTValueAnimatedNode *valueNode in _updatedValueNodes) {
|
||||
[valueNode cleanupAnimationUpdate];
|
||||
}
|
||||
[_updatedValueNodes removeAllObjects];
|
||||
|
||||
for (id<RCTAnimationDriver> driverNode in _activeAnimations) {
|
||||
if (driverNode.animationHasFinished) {
|
||||
[driverNode removeAnimation];
|
||||
[_finishedAnimations addObject:driverNode];
|
||||
}
|
||||
}
|
||||
for (id<RCTAnimationDriver> driverNode in _finishedAnimations) {
|
||||
[_activeAnimations removeObject:driverNode];
|
||||
[_animationDrivers removeObjectForKey:driverNode.animationId];
|
||||
}
|
||||
[_finishedAnimations removeAllObjects];
|
||||
|
||||
[self stopAnimationLoopIfNeeded];
|
||||
return [_nodesManager handleAnimatedEvent:event];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
#import "RCTValueAnimatedNode.h"
|
||||
|
||||
@interface RCTNativeAnimatedNodesManager : NSObject
|
||||
|
||||
- (nonnull instancetype)initWithUIManager:(nonnull RCTUIManager *)uiManager;
|
||||
|
||||
- (void)updateAnimations;
|
||||
|
||||
// graph
|
||||
|
||||
- (void)createAnimatedNode:(nonnull NSNumber *)tag
|
||||
config:(NSDictionary<NSString *, id> *__nonnull)config;
|
||||
|
||||
- (void)connectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag;
|
||||
|
||||
- (void)disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag;
|
||||
|
||||
- (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag;
|
||||
|
||||
- (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag;
|
||||
|
||||
- (void)dropAnimatedNode:(nonnull NSNumber *)tag;
|
||||
|
||||
// mutations
|
||||
|
||||
- (void)setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
|
||||
value:(nonnull NSNumber *)value;
|
||||
|
||||
- (void)setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
|
||||
offset:(nonnull NSNumber *)offset;
|
||||
|
||||
- (void)flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag;
|
||||
|
||||
- (void)extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag;
|
||||
|
||||
// drivers
|
||||
|
||||
- (void)startAnimatingNode:(nonnull NSNumber *)animationId
|
||||
nodeTag:(nonnull NSNumber *)nodeTag
|
||||
config:(NSDictionary<NSString *, id> *__nonnull)config
|
||||
endCallback:(nullable RCTResponseSenderBlock)callBack;
|
||||
|
||||
- (void)stopAnimation:(nonnull NSNumber *)animationId;
|
||||
|
||||
// events
|
||||
|
||||
- (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName
|
||||
eventMapping:(NSDictionary<NSString *, id> *__nonnull)eventMapping;
|
||||
|
||||
- (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName;
|
||||
|
||||
- (void)handleAnimatedEvent:(nonnull id<RCTEvent>)event;
|
||||
|
||||
// listeners
|
||||
|
||||
- (void)startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
|
||||
valueObserver:(nonnull id<RCTValueAnimatedNodeObserver>)valueObserver;
|
||||
|
||||
- (void)stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
|
||||
valueObserver:(nonnull id<RCTValueAnimatedNodeObserver>)valueObserver;
|
||||
|
||||
@end
|
|
@ -0,0 +1,360 @@
|
|||
/**
|
||||
* 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 "RCTNativeAnimatedNodesManager.h"
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "RCTAnimatedNode.h"
|
||||
#import "RCTAnimationDriver.h"
|
||||
#import "RCTEventAnimation.h"
|
||||
|
||||
#import "RCTAdditionAnimatedNode.h"
|
||||
#import "RCTInterpolationAnimatedNode.h"
|
||||
#import "RCTDiffClampAnimatedNode.h"
|
||||
#import "RCTDivisionAnimatedNode.h"
|
||||
#import "RCTModuloAnimatedNode.h"
|
||||
#import "RCTMultiplicationAnimatedNode.h"
|
||||
#import "RCTModuloAnimatedNode.h"
|
||||
#import "RCTPropsAnimatedNode.h"
|
||||
#import "RCTStyleAnimatedNode.h"
|
||||
#import "RCTTransformAnimatedNode.h"
|
||||
#import "RCTValueAnimatedNode.h"
|
||||
#import "RCTFrameAnimation.h"
|
||||
#import "RCTSpringAnimation.h"
|
||||
|
||||
@implementation RCTNativeAnimatedNodesManager
|
||||
{
|
||||
RCTUIManager *_uiManager;
|
||||
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
|
||||
NSMutableDictionary<NSString *, RCTEventAnimation *> *_eventDrivers;
|
||||
NSMutableSet<id<RCTAnimationDriver>> *_activeAnimations;
|
||||
CADisplayLink *_displayLink;
|
||||
}
|
||||
|
||||
- (instancetype)initWithUIManager:(nonnull RCTUIManager *)uiManager
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_uiManager = uiManager;
|
||||
_animationNodes = [NSMutableDictionary new];
|
||||
_eventDrivers = [NSMutableDictionary new];
|
||||
_activeAnimations = [NSMutableSet new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -- Graph
|
||||
|
||||
- (void)createAnimatedNode:(nonnull NSNumber *)tag
|
||||
config:(NSDictionary<NSString *, id> *)config
|
||||
{
|
||||
static NSDictionary *map;
|
||||
static dispatch_once_t mapToken;
|
||||
dispatch_once(&mapToken, ^{
|
||||
map = @{@"style" : [RCTStyleAnimatedNode class],
|
||||
@"value" : [RCTValueAnimatedNode class],
|
||||
@"props" : [RCTPropsAnimatedNode class],
|
||||
@"interpolation" : [RCTInterpolationAnimatedNode class],
|
||||
@"addition" : [RCTAdditionAnimatedNode class],
|
||||
@"diffclamp": [RCTDiffClampAnimatedNode class],
|
||||
@"division" : [RCTDivisionAnimatedNode class],
|
||||
@"multiplication" : [RCTMultiplicationAnimatedNode class],
|
||||
@"modulus" : [RCTModuloAnimatedNode class],
|
||||
@"transform" : [RCTTransformAnimatedNode class]};
|
||||
});
|
||||
|
||||
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
|
||||
|
||||
Class nodeClass = map[nodeType];
|
||||
if (!nodeClass) {
|
||||
RCTLogError(@"Animated node type %@ not supported natively", nodeType);
|
||||
return;
|
||||
}
|
||||
|
||||
RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config];
|
||||
_animationNodes[tag] = node;
|
||||
[node setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)connectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag
|
||||
{
|
||||
RCTAssertParam(parentTag);
|
||||
RCTAssertParam(childTag);
|
||||
|
||||
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
|
||||
RCTAnimatedNode *childNode = _animationNodes[childTag];
|
||||
|
||||
RCTAssertParam(parentNode);
|
||||
RCTAssertParam(childNode);
|
||||
|
||||
[parentNode addChild:childNode];
|
||||
[childNode setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag
|
||||
{
|
||||
RCTAssertParam(parentTag);
|
||||
RCTAssertParam(childTag);
|
||||
|
||||
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
|
||||
RCTAnimatedNode *childNode = _animationNodes[childTag];
|
||||
|
||||
RCTAssertParam(parentNode);
|
||||
RCTAssertParam(childNode);
|
||||
|
||||
[parentNode removeChild:childNode];
|
||||
[childNode setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[(RCTPropsAnimatedNode *)node connectToView:viewTag uiManager:_uiManager];
|
||||
}
|
||||
[node setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (viewTag && node && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dropAnimatedNode:(nonnull NSNumber *)tag
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node) {
|
||||
[node detachNode];
|
||||
[_animationNodes removeObjectForKey:tag];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Mutations
|
||||
|
||||
- (void)setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
|
||||
value:(nonnull NSNumber *)value
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
valueNode.value = value.floatValue;
|
||||
[valueNode setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
|
||||
offset:(nonnull NSNumber *)offset
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode setOffset:offset.floatValue];
|
||||
[valueNode setNeedsUpdate];
|
||||
}
|
||||
|
||||
- (void)flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode flattenOffset];
|
||||
}
|
||||
|
||||
- (void)extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode extractOffset];
|
||||
}
|
||||
|
||||
#pragma mark -- Drivers
|
||||
|
||||
- (void)startAnimatingNode:(nonnull NSNumber *)animationId
|
||||
nodeTag:(nonnull NSNumber *)nodeTag
|
||||
config:(NSDictionary<NSString *, id> *)config
|
||||
endCallback:(RCTResponseSenderBlock)callBack
|
||||
{
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag];
|
||||
|
||||
NSString *type = config[@"type"];
|
||||
id<RCTAnimationDriver>animationDriver;
|
||||
|
||||
if ([type isEqual:@"frames"]) {
|
||||
animationDriver = [[RCTFrameAnimation alloc] initWithId:animationId
|
||||
config:config
|
||||
forNode:valueNode
|
||||
callBack:callBack];
|
||||
|
||||
} else if ([type isEqual:@"spring"]) {
|
||||
animationDriver = [[RCTSpringAnimation alloc] initWithId:animationId
|
||||
config:config
|
||||
forNode:valueNode
|
||||
callBack:callBack];
|
||||
|
||||
} else {
|
||||
RCTLogError(@"Unsupported animation type: %@", config[@"type"]);
|
||||
return;
|
||||
}
|
||||
|
||||
[_activeAnimations addObject:animationDriver];
|
||||
[animationDriver startAnimation];
|
||||
[self startAnimationLoopIfNeeded];
|
||||
}
|
||||
|
||||
- (void)stopAnimation:(nonnull NSNumber *)animationId
|
||||
{
|
||||
for (id<RCTAnimationDriver>driver in _activeAnimations) {
|
||||
if ([driver.animationId isEqual:animationId]) {
|
||||
[driver removeAnimation];
|
||||
[_activeAnimations removeObject:driver];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Events
|
||||
|
||||
- (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName
|
||||
eventMapping:(NSDictionary<NSString *, id> *)eventMapping
|
||||
{
|
||||
NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]];
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
|
||||
if (!node) {
|
||||
RCTLogError(@"Animated node with tag %@ does not exists", nodeTag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Animated node connected to event should be of type RCTValueAnimatedNode");
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<NSString *> *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]];
|
||||
|
||||
RCTEventAnimation *driver =
|
||||
[[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node];
|
||||
|
||||
_eventDrivers[[NSString stringWithFormat:@"%@%@", viewTag, eventName]] = driver;
|
||||
}
|
||||
|
||||
- (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName
|
||||
{
|
||||
[_eventDrivers removeObjectForKey:[NSString stringWithFormat:@"%@%@", viewTag, eventName]];
|
||||
}
|
||||
|
||||
- (void)handleAnimatedEvent:(id<RCTEvent>)event
|
||||
{
|
||||
if (_eventDrivers.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName];
|
||||
RCTEventAnimation *driver = _eventDrivers[key];
|
||||
if (driver) {
|
||||
[driver updateWithEvent:event];
|
||||
[self updateAnimations];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Listeners
|
||||
|
||||
- (void)startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
|
||||
valueObserver:(id<RCTValueAnimatedNodeObserver>)valueObserver
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
((RCTValueAnimatedNode *)node).valueObserver = valueObserver;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
|
||||
valueObserver:(id<RCTValueAnimatedNodeObserver>)valueObserver
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
((RCTValueAnimatedNode *)node).valueObserver = valueObserver;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -- Animation Loop
|
||||
|
||||
- (void)startAnimationLoopIfNeeded
|
||||
{
|
||||
if (!_displayLink && _activeAnimations.count > 0) {
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepAnimations)];
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAnimationLoopIfNeeded
|
||||
{
|
||||
if (_displayLink && _activeAnimations.count == 0) {
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stepAnimations
|
||||
{
|
||||
for (id<RCTAnimationDriver>animationDriver in _activeAnimations) {
|
||||
[animationDriver stepAnimation];
|
||||
}
|
||||
|
||||
[self updateAnimations];
|
||||
|
||||
for (id<RCTAnimationDriver>animationDriver in [_activeAnimations copy]) {
|
||||
if (animationDriver.animationHasFinished) {
|
||||
[animationDriver removeAnimation];
|
||||
[_activeAnimations removeObject:animationDriver];
|
||||
}
|
||||
}
|
||||
|
||||
[self stopAnimationLoopIfNeeded];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -- Updates
|
||||
|
||||
- (void)updateAnimations
|
||||
{
|
||||
[_animationNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, RCTAnimatedNode *node, BOOL *stop) {
|
||||
if (node.needsUpdate) {
|
||||
[node updateNodeIfNecessary];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RCTNativeAnimatedModule;
|
||||
@class RCTUIManager;
|
||||
|
||||
@interface RCTViewPropertyMapper : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSNumber *viewTag;
|
||||
|
||||
- (instancetype)initWithViewTag:(NSNumber *)viewTag
|
||||
animationModule:(RCTNativeAnimatedModule *)animationModule NS_DESIGNATED_INITIALIZER;
|
||||
uiManager:(RCTUIManager *)uiManager NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSObject *> *)updates;
|
||||
|
||||
|
|
|
@ -17,41 +17,46 @@
|
|||
|
||||
#import "RCTNativeAnimatedModule.h"
|
||||
|
||||
@interface RCTViewPropertyMapper ()
|
||||
|
||||
@property (nonatomic, weak) UIView *cachedView;
|
||||
@property (nonatomic, weak) RCTUIManager *uiManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTViewPropertyMapper
|
||||
{
|
||||
RCTNativeAnimatedModule *_animationModule;
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewTag:(NSNumber *)viewTag
|
||||
animationModule:(RCTNativeAnimatedModule *)animationModule
|
||||
uiManager:(RCTUIManager *)uiManager
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_animationModule = animationModule;
|
||||
_uiManager = uiManager;
|
||||
_viewTag = viewTag;
|
||||
_animationModule = animationModule;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
|
||||
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSObject *> *)updates
|
||||
- (void)updateViewWithDictionary:(NSDictionary<NSString *, NSObject *> *)properties
|
||||
{
|
||||
if (!updates.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIView *view = [_animationModule.bridge.uiManager viewForReactTag:_viewTag];
|
||||
// cache the view for perf reasons (avoid constant lookups)
|
||||
UIView *view = _cachedView = _cachedView ?: [self.uiManager viewForReactTag:_viewTag];
|
||||
if (!view) {
|
||||
RCTLogError(@"No view to update.");
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *opacity = [RCTConvert NSNumber:updates[@"opacity"]];
|
||||
if (!properties.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *opacity = [RCTConvert NSNumber:properties[@"opacity"]];
|
||||
if (opacity) {
|
||||
view.alpha = opacity.floatValue;
|
||||
}
|
||||
|
||||
NSObject *transform = updates[@"transform"];
|
||||
NSObject *transform = properties[@"transform"];
|
||||
if ([transform isKindOfClass:[NSValue class]]) {
|
||||
view.layer.allowsEdgeAntialiasing = YES;
|
||||
view.layer.transform = ((NSValue *)transform).CATransform3DValue;
|
||||
|
|
Loading…
Reference in New Issue