From 7e869b9d0a5966cb026d86067c0e000b31bf6535 Mon Sep 17 00:00:00 2001 From: Ryan Gomba Date: Thu, 26 Jan 2017 18:14:40 -0800 Subject: [PATCH] Drive any numerical prop via NativeAnimated Summary: In theory, we should be able to animate any non-layout property, including custom ones. While there is still work to be done on the native side to fully enable this, we should start by dropping the prop whitelist. Closes https://github.com/facebook/react-native/pull/10658 Differential Revision: D4379031 Pulled By: ericvicenti fbshipit-source-id: fe9c30ea101e93a8b260d7d09a909fafbb82fee6 --- .../UIExplorer/js/NativeAnimationsExample.js | 22 ++- .../Animated/src/AnimatedImplementation.js | 1 - .../Animated/src/NativeAnimatedHelper.js | 33 ++--- .../Nodes/RCTPropsAnimatedNode.h | 7 +- .../Nodes/RCTPropsAnimatedNode.m | 41 ++++-- .../Nodes/RCTTransformAnimatedNode.m | 41 +----- .../RCTAnimation.xcodeproj/project.pbxproj | 10 +- .../NativeAnimation/RCTNativeAnimatedModule.m | 3 +- .../RCTNativeAnimatedNodesManager.h | 3 +- .../RCTNativeAnimatedNodesManager.m | 11 +- .../NativeAnimation/RCTViewPropertyMapper.h | 23 --- .../NativeAnimation/RCTViewPropertyMapper.m | 66 --------- Libraries/StyleSheet/processTransform.js | 8 +- React/Base/RCTConvert.h | 1 - React/Base/RCTConvert.m | 7 - React/Modules/RCTUIManager.h | 15 ++ React/Modules/RCTUIManager.m | 16 +++ React/React.xcodeproj/project.pbxproj | 9 ++ React/Views/RCTConvert+Transform.h | 16 +++ React/Views/RCTConvert+Transform.m | 136 ++++++++++++++++++ React/Views/RCTViewManager.m | 1 + 21 files changed, 271 insertions(+), 199 deletions(-) delete mode 100644 Libraries/NativeAnimation/RCTViewPropertyMapper.h delete mode 100644 Libraries/NativeAnimation/RCTViewPropertyMapper.m create mode 100644 React/Views/RCTConvert+Transform.h create mode 100644 React/Views/RCTConvert+Transform.m diff --git a/Examples/UIExplorer/js/NativeAnimationsExample.js b/Examples/UIExplorer/js/NativeAnimationsExample.js index 208f27a649d2cc..25c6fd261ff85d 100644 --- a/Examples/UIExplorer/js/NativeAnimationsExample.js +++ b/Examples/UIExplorer/js/NativeAnimationsExample.js @@ -30,8 +30,11 @@ const { Animated, StyleSheet, TouchableWithoutFeedback, + Slider, } = ReactNative; +var AnimatedSlider = Animated.createAnimatedComponent(Slider); + class Tester extends React.Component { state = { native: new Animated.Value(0), @@ -228,7 +231,6 @@ exports.description = 'Test out Native Animations'; exports.examples = [ { title: 'Multistage With Multiply and rotation', - description: 'description', render: function() { return ( Animated.spring', - description: 'description', render: function() { return ( ); }, + },{ + title: 'Drive custom property', + render: function() { + return ( + + {anim => ( + + )} + + ); + }, }, { title: 'Animated value listener', diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 6dae493300eff8..ad9bdc6a366234 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -1676,7 +1676,6 @@ class AnimatedProps extends Animated { propsConfig[propKey] = value.__getNativeTag(); } } - NativeAnimatedHelper.validateProps(propsConfig); return { type: 'props', props: propsConfig, diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js index 168451a47f9715..9949155ec96fe8 100644 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ b/Libraries/Animated/src/NativeAnimatedHelper.js @@ -100,22 +100,19 @@ const API = { }; /** - * Properties allowed by the native animated implementation. + * Styles allowed by the native animated implementation. * * In general native animated implementation should support any numeric property that doesn't need - * to be updated through the shadow view hierarchy (all non-layout properties). This list is limited - * to the properties that will perform best when animated off the JS thread. + * to be updated through the shadow view hierarchy (all non-layout properties). */ -const PROPS_WHITELIST = { - style: { - opacity: true, - transform: true, - /* legacy android transform properties */ - scaleX: true, - scaleY: true, - translateX: true, - translateY: true, - }, +const STYLES_WHITELIST = { + opacity: true, + transform: true, + /* legacy android transform properties */ + scaleX: true, + scaleY: true, + translateX: true, + translateY: true, }; const TRANSFORM_WHITELIST = { @@ -130,14 +127,6 @@ const TRANSFORM_WHITELIST = { perspective: true, }; -function validateProps(params: Object): void { - for (var key in params) { - if (!PROPS_WHITELIST.hasOwnProperty(key)) { - throw new Error(`Property '${key}' is not supported by native animated module`); - } - } -} - function validateTransform(configs: Array): void { configs.forEach((config) => { if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) { @@ -147,7 +136,6 @@ function validateTransform(configs: Array): void { } function validateStyles(styles: Object): void { - var STYLES_WHITELIST = PROPS_WHITELIST.style || {}; for (var key in styles) { if (!STYLES_WHITELIST.hasOwnProperty(key)) { throw new Error(`Style property '${key}' is not supported by native animated module`); @@ -188,7 +176,6 @@ function isNativeAnimatedAvailable(): boolean { module.exports = { API, - validateProps, validateStyles, validateTransform, validateInterpolation, diff --git a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h index 5613f61407ac8d..64edebf7681246 100644 --- a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h +++ b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h @@ -14,11 +14,10 @@ @interface RCTPropsAnimatedNode : RCTAnimatedNode -@property (nonatomic, readonly) RCTViewPropertyMapper *propertyMapper; +- (void)connectToView:(NSNumber *)viewTag + viewName:(NSString *)viewName + uiManager:(RCTUIManager *)uiManager; -- (void)connectToView:(NSNumber *)viewTag uiManager:(RCTUIManager *)uiManager; - (void)disconnectFromView:(NSNumber *)viewTag; -- (void)performViewUpdatesIfNecessary; - @end diff --git a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m index a2017405b1c61e..76b146e525e07a 100644 --- a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m @@ -9,27 +9,33 @@ #import "RCTPropsAnimatedNode.h" +#import +#import + #import "RCTAnimationUtils.h" #import "RCTStyleAnimatedNode.h" #import "RCTValueAnimatedNode.h" -#import "RCTViewPropertyMapper.h" - -@implementation RCTPropsAnimatedNode -- (void)connectToView:(NSNumber *)viewTag uiManager:(RCTUIManager *)uiManager -{ - _propertyMapper = [[RCTViewPropertyMapper alloc] initWithViewTag:viewTag uiManager:uiManager]; +@implementation RCTPropsAnimatedNode { + NSNumber *_connectedViewTag; + NSString *_connectedViewName; + RCTUIManager *_uiManager; } -- (void)disconnectFromView:(NSNumber *)viewTag +- (void)connectToView:(NSNumber *)viewTag + viewName:(NSString *)viewName + uiManager:(RCTUIManager *)uiManager { - _propertyMapper = nil; + _connectedViewTag = viewTag; + _connectedViewName = viewName; + _uiManager = uiManager; } -- (void)performUpdate +- (void)disconnectFromView:(NSNumber *)viewTag { - [super performUpdate]; - [self performViewUpdatesIfNecessary]; + _connectedViewTag = nil; + _connectedViewName = nil; + _uiManager = nil; } - (NSString *)propertyNameForParentTag:(NSNumber *)parentTag @@ -44,8 +50,15 @@ - (NSString *)propertyNameForParentTag:(NSNumber *)parentTag return propertyName; } -- (void)performViewUpdatesIfNecessary +- (void)performUpdate { + [super performUpdate]; + + if (!_connectedViewTag) { + RCTLogError(@"Node has not been attached to a view"); + return; + } + NSMutableDictionary *props = [NSMutableDictionary dictionary]; [self.parentNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull parentTag, RCTAnimatedNode * _Nonnull parentNode, BOOL * _Nonnull stop) { @@ -61,7 +74,9 @@ - (void)performViewUpdatesIfNecessary }]; if (props.count) { - [_propertyMapper updateViewWithDictionary:props]; + [_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag + viewName:_connectedViewName + props:props]; } } diff --git a/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m index 07a89344b5d6df..91b681fdc395d2 100644 --- a/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m @@ -33,14 +33,12 @@ - (void)performUpdate { [super performUpdate]; - CATransform3D transform = CATransform3DIdentity; - NSArray *transformConfigs = self.config[@"transforms"]; + NSMutableArray *transform = [NSMutableArray arrayWithCapacity:transformConfigs.count]; for (NSDictionary *transformConfig in transformConfigs) { NSString *type = transformConfig[@"type"]; NSString *property = transformConfig[@"property"]; - - CGFloat value; + NSNumber *value; if ([type isEqualToString: @"animated"]) { NSNumber *nodeTag = transformConfig[@"nodeTag"]; RCTAnimatedNode *node = self.parentNodes[nodeTag]; @@ -48,41 +46,14 @@ - (void)performUpdate continue; } RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node; - value = parentNode.value; + value = @(parentNode.value); } else { - value = [transformConfig[@"value"] floatValue]; - } - - if ([property isEqualToString:@"scale"]) { - transform = CATransform3DScale(transform, value, value, 1); - - } else if ([property isEqualToString:@"scaleX"]) { - transform = CATransform3DScale(transform, value, 1, 1); - - } else if ([property isEqualToString:@"scaleY"]) { - transform = CATransform3DScale(transform, 1, value, 1); - - } else if ([property isEqualToString:@"translateX"]) { - transform = CATransform3DTranslate(transform, value, 0, 0); - - } else if ([property isEqualToString:@"translateY"]) { - transform = CATransform3DTranslate(transform, 0, value, 0); - - } else if ([property isEqualToString:@"rotate"]) { - transform = CATransform3DRotate(transform, value, 0, 0, 1); - - } else if ([property isEqualToString:@"rotateX"]) { - transform = CATransform3DRotate(transform, value, 1, 0, 0); - - } else if ([property isEqualToString:@"rotateY"]) { - transform = CATransform3DRotate(transform, value, 0, 1, 0); - - } else if ([property isEqualToString:@"perspective"]) { - transform.m34 = 1.0 / -value; + value = transformConfig[@"value"]; } + [transform addObject:@{property: value}]; } - _propsDictionary[@"transform"] = [NSValue valueWithCATransform3D:transform]; + _propsDictionary[@"transform"] = transform; } @end diff --git a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj index 06d377870d510b..5150f0d3522d46 100644 --- a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj +++ b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 13E501CC1D07A644005F35D8 /* RCTAnimationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */; }; 13E501CF1D07A644005F35D8 /* RCTNativeAnimatedModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */; }; - 13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */; }; 13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */; }; 13E501E91D07A6C9005F35D8 /* RCTAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */; }; 13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */; }; @@ -22,7 +21,6 @@ 19F00F221DC8847500113FEE /* RCTEventAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F00F211DC8847500113FEE /* RCTEventAnimation.m */; }; 19F00F231DC8848E00113FEE /* RCTEventAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F00F211DC8847500113FEE /* RCTEventAnimation.m */; }; 2D3B5EF21D9B0B3100451313 /* RCTAnimationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */; }; - 2D3B5EF31D9B0B3400451313 /* RCTViewPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */; }; 2D3B5EF41D9B0B3700451313 /* RCTNativeAnimatedModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */; }; 2D3B5EF51D9B0B4800451313 /* RCTDivisionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */; }; 2D3B5EF61D9B0B4800451313 /* RCTDiffClampAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */; }; @@ -51,9 +49,7 @@ 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationUtils.m; sourceTree = ""; }; 13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTNativeAnimatedModule.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNativeAnimatedModule.m; sourceTree = ""; }; - 13E501C71D07A644005F35D8 /* RCTViewPropertyMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewPropertyMapper.h; sourceTree = ""; }; - 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTViewPropertyMapper.m; sourceTree = ""; }; - 13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTAdditionAnimatedNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAdditionAnimatedNode.h; sourceTree = ""; }; 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAdditionAnimatedNode.m; sourceTree = ""; }; 13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimatedNode.h; sourceTree = ""; }; 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimatedNode.m; sourceTree = ""; }; @@ -130,8 +126,6 @@ children = ( 13E501B71D07A644005F35D8 /* RCTAnimationUtils.h */, 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */, - 13E501C71D07A644005F35D8 /* RCTViewPropertyMapper.h */, - 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */, 13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */, 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */, 94DA09161DC7971C00AEA8C9 /* RCTNativeAnimatedNodesManager.h */, @@ -245,7 +239,6 @@ 2D3B5EFF1D9B0B4800451313 /* RCTTransformAnimatedNode.m in Sources */, 2D3B5EFC1D9B0B4800451313 /* RCTMultiplicationAnimatedNode.m in Sources */, 2D3B5EFD1D9B0B4800451313 /* RCTPropsAnimatedNode.m in Sources */, - 2D3B5EF31D9B0B3400451313 /* RCTViewPropertyMapper.m in Sources */, 944244D01DB962DA0032A02B /* RCTFrameAnimation.m in Sources */, 944244D11DB962DC0032A02B /* RCTSpringAnimation.m in Sources */, 9476E8EC1DC9232D005D5CD1 /* RCTNativeAnimatedNodesManager.m in Sources */, @@ -273,7 +266,6 @@ 13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */, 5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */, 13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */, - 13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 53e67eb7745b4f..55763bfe57dc28 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -121,8 +121,9 @@ - (void)setBridge:(RCTBridge *)bridge RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag) { + NSString *viewName = [self.bridge.uiManager viewNameForReactTag:viewTag]; [_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) { - [nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag]; + [nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag viewName:viewName]; }]; } diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h index 1330aad49de040..e911f1b37c2dd5 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h @@ -32,7 +32,8 @@ childTag:(nonnull NSNumber *)childTag; - (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag - viewTag:(nonnull NSNumber *)viewTag; + viewTag:(nonnull NSNumber *)viewTag + viewName:(nonnull NSString *)viewName; - (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag; diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m index c1dc22fabf5e0f..ac15ad705ddd3c 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m @@ -116,10 +116,11 @@ - (void)disconnectAnimatedNodes:(nonnull NSNumber *)parentTag - (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag + viewName:(nonnull NSString *)viewName { RCTAnimatedNode *node = _animationNodes[nodeTag]; - if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) { - [(RCTPropsAnimatedNode *)node connectToView:viewTag uiManager:_uiManager]; + if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { + [(RCTPropsAnimatedNode *)node connectToView:viewTag viewName:viewName uiManager:_uiManager]; } [node setNeedsUpdate]; } @@ -128,7 +129,7 @@ - (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; - if (viewTag && node && [node isKindOfClass:[RCTPropsAnimatedNode class]]) { + if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { [(RCTPropsAnimatedNode *)node disconnectFromView:viewTag]; } } @@ -294,7 +295,7 @@ - (void)startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag valueObserver:(id)valueObserver { RCTAnimatedNode *node = _animationNodes[tag]; - if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) { + if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = valueObserver; } } @@ -303,7 +304,7 @@ - (void)stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag valueObserver:(id)valueObserver { RCTAnimatedNode *node = _animationNodes[tag]; - if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) { + if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = valueObserver; } } diff --git a/Libraries/NativeAnimation/RCTViewPropertyMapper.h b/Libraries/NativeAnimation/RCTViewPropertyMapper.h deleted file mode 100644 index 6f2a98f116ed5a..00000000000000 --- a/Libraries/NativeAnimation/RCTViewPropertyMapper.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 - -@class RCTUIManager; - -@interface RCTViewPropertyMapper : NSObject - -@property (nonatomic, readonly) NSNumber *viewTag; - -- (instancetype)initWithViewTag:(NSNumber *)viewTag - uiManager:(RCTUIManager *)uiManager NS_DESIGNATED_INITIALIZER; - -- (void)updateViewWithDictionary:(NSDictionary *)updates; - -@end diff --git a/Libraries/NativeAnimation/RCTViewPropertyMapper.m b/Libraries/NativeAnimation/RCTViewPropertyMapper.m deleted file mode 100644 index 40c8b5625935cb..00000000000000 --- a/Libraries/NativeAnimation/RCTViewPropertyMapper.m +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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 "RCTViewPropertyMapper.h" - -#import - -#import -#import -#import - -#import "RCTNativeAnimatedModule.h" - -@interface RCTViewPropertyMapper () - -@property (nonatomic, weak) UIView *cachedView; -@property (nonatomic, weak) RCTUIManager *uiManager; - -@end - -@implementation RCTViewPropertyMapper - -- (instancetype)initWithViewTag:(NSNumber *)viewTag - uiManager:(RCTUIManager *)uiManager -{ - if ((self = [super init])) { - _uiManager = uiManager; - _viewTag = viewTag; - } - return self; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)init) - -- (void)updateViewWithDictionary:(NSDictionary *)properties -{ - // 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; - } - - if (!properties.count) { - return; - } - - NSNumber *opacity = [RCTConvert NSNumber:properties[@"opacity"]]; - if (opacity) { - view.alpha = opacity.floatValue; - } - - NSObject *transform = properties[@"transform"]; - if ([transform isKindOfClass:[NSValue class]]) { - view.layer.allowsEdgeAntialiasing = YES; - view.layer.transform = ((NSValue *)transform).CATransform3DValue; - } -} - -@end diff --git a/Libraries/StyleSheet/processTransform.js b/Libraries/StyleSheet/processTransform.js index bddd54f54548c9..a00dc1d0196b9b 100644 --- a/Libraries/StyleSheet/processTransform.js +++ b/Libraries/StyleSheet/processTransform.js @@ -30,10 +30,10 @@ function processTransform(transform: Object): Object { _validateTransforms(transform); } - // Android implementation of transform property accepts the list of transform - // properties as opposed to a transform Matrix. This is necessary to control - // transform property updates completely on the native thread. - if (Platform.OS === 'android') { + // Android & iOS implementations of transform property accept the list of + // transform properties as opposed to a transform Matrix. This is necessary + // to control transform property updates completely on the native thread. + if (Platform.OS === 'android' || Platform.OS === 'ios') { return transform; } diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index a71cc7857e3f10..520c216b54cb30 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -85,7 +85,6 @@ typedef NSURL RCTFileURL; + (CGLineCap)CGLineCap:(id)json; + (CGLineJoin)CGLineJoin:(id)json; -+ (CATransform3D)CATransform3D:(id)json; + (CGAffineTransform)CGAffineTransform:(id)json; + (UIColor *)UIColor:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 084caec3494cde..1a33d84b676d84 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -465,13 +465,6 @@ + (type)type:(id)json \ @"square": @(kCGLineCapSquare), }), kCGLineCapButt, intValue) -RCT_CGSTRUCT_CONVERTER(CATransform3D, (@[ - @"m11", @"m12", @"m13", @"m14", - @"m21", @"m22", @"m23", @"m24", - @"m31", @"m32", @"m33", @"m34", - @"m41", @"m42", @"m43", @"m44" -]), nil) - RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ @"a", @"b", @"c", @"d", @"tx", @"ty" ]), nil) diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index e31ca39ec8ce1c..421bea883c5490 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -60,6 +60,11 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ - (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility; +/** + * Gets the view name associated with a reactTag. + */ +- (NSString *)viewNameForReactTag:(NSNumber *)reactTag; + /** * Gets the view associated with a reactTag. */ @@ -90,6 +95,16 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ - (void)addUIBlock:(RCTViewManagerUIBlock)block; +/** + * Used by native animated module to bypass the process of updating the values through the shadow + * view hierarchy. This method will directly update native views, which means that updates for + * layout-related propertied won't be handled properly. + * Make sure you know what you're doing before calling this method :) + */ +- (void)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag + viewName:(NSString *)viewName + props:(NSDictionary *)props; + /** * Given a reactTag from a component, find its root view, if possible. * Otherwise, this will give back nil. diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index f0a3bfaac516d1..3777540492caab 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -413,6 +413,12 @@ - (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSize userInfo:@{RCTUIManagerRootViewKey: rootView}]; } +- (NSString *)viewNameForReactTag:(NSNumber *)reactTag +{ + RCTAssertThread(RCTGetUIManagerQueue(), @"viewNameForReactTag can only be called from the shadow queue"); + return _shadowViewRegistry[reactTag].viewName; +} + - (UIView *)viewForReactTag:(NSNumber *)reactTag { RCTAssertMainQueue(); @@ -1053,6 +1059,16 @@ - (void)_manageChildren:(NSNumber *)containerTag }]; } +- (void)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag + viewName:(NSString *)viewName + props:(NSDictionary *)props +{ + RCTAssertMainQueue(); + RCTComponentData *componentData = _componentDataByName[viewName]; + UIView *view = _viewRegistry[reactTag]; + [componentData setProps:props forView:view]; +} + RCT_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag) { [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index e335b88d24200e..0cb26160beacef 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -721,6 +721,9 @@ A2440AA31DF8D854006E7BFC /* RCTReloadCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = A2440AA11DF8D854006E7BFC /* RCTReloadCommand.m */; }; A2440AA41DF8D865006E7BFC /* RCTReloadCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = A2440AA01DF8D854006E7BFC /* RCTReloadCommand.h */; }; AC70D2E91DE489E4002E6351 /* RCTJavaScriptLoader.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC70D2E81DE489E4002E6351 /* RCTJavaScriptLoader.mm */; }; + AC70D2ED1DE48A22002E6351 /* JSBundleType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AC70D2EB1DE48A22002E6351 /* JSBundleType.cpp */; }; + 945929C41DD62ADD00653A7D /* RCTConvert+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */; }; + 945929C51DD62ADD00653A7D /* RCTConvert+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */; }; B233E6EA1D2D845D00BC68BA /* RCTI18nManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B233E6E91D2D845D00BC68BA /* RCTI18nManager.m */; }; B95154321D1B34B200FE7B80 /* RCTActivityIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = B95154311D1B34B200FE7B80 /* RCTActivityIndicatorView.m */; }; E9B20B7B1B500126007A2DA7 /* RCTAccessibilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */; }; @@ -1356,6 +1359,8 @@ AC70D2E81DE489E4002E6351 /* RCTJavaScriptLoader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTJavaScriptLoader.mm; sourceTree = ""; }; AC70D2EB1DE48A22002E6351 /* JSBundleType.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSBundleType.cpp; sourceTree = ""; }; AC70D2EE1DE48AC5002E6351 /* oss-compat-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "oss-compat-util.h"; sourceTree = ""; }; + 945929C21DD62ADD00653A7D /* RCTConvert+Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Transform.h"; sourceTree = ""; }; + 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Transform.m"; sourceTree = ""; }; ACDD3FDA1BC7430D00E7DE33 /* RCTBorderStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderStyle.h; sourceTree = ""; }; B233E6E81D2D843200BC68BA /* RCTI18nManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTI18nManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; B233E6E91D2D845D00BC68BA /* RCTI18nManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTI18nManager.m; sourceTree = ""; }; @@ -1479,6 +1484,8 @@ 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */, 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */, 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */, + 945929C21DD62ADD00653A7D /* RCTConvert+Transform.h */, + 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */, 133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */, 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */, 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */, @@ -2417,6 +2424,7 @@ 2D3B5E941D9B087900451313 /* RCTBundleURLProvider.m in Sources */, 2D3B5EB81D9B091B00451313 /* RCTSourceCode.m in Sources */, 2D3B5EB51D9B091100451313 /* RCTDevMenu.mm in Sources */, + 945929C51DD62ADD00653A7D /* RCTConvert+Transform.m in Sources */, 2D3B5EBD1D9B092A00451313 /* RCTTiming.m in Sources */, 2D3B5EA81D9B08D300451313 /* RCTUtils.m in Sources */, 2D3B5EC81D9B095800451313 /* RCTActivityIndicatorViewManager.m in Sources */, @@ -2605,6 +2613,7 @@ 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */, 391E86A41C623EC800009732 /* RCTTouchEvent.m in Sources */, 1450FF861BCFF28A00208362 /* RCTProfile.m in Sources */, + 945929C41DD62ADD00653A7D /* RCTConvert+Transform.m in Sources */, 13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */, 13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */, ); diff --git a/React/Views/RCTConvert+Transform.h b/React/Views/RCTConvert+Transform.h new file mode 100644 index 00000000000000..29c65823bcb26c --- /dev/null +++ b/React/Views/RCTConvert+Transform.h @@ -0,0 +1,16 @@ +/** + * 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 "RCTConvert.h" + +@interface RCTConvert (Transform) + ++ (CATransform3D)CATransform3D:(id)json; + +@end diff --git a/React/Views/RCTConvert+Transform.m b/React/Views/RCTConvert+Transform.m new file mode 100644 index 00000000000000..1286553c5af87d --- /dev/null +++ b/React/Views/RCTConvert+Transform.m @@ -0,0 +1,136 @@ +/** + * 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 "RCTConvert+Transform.h" + +static const NSUInteger kMatrixArrayLength = 4 * 4; + +@implementation RCTConvert (Transform) + ++ (CGFloat)convertToRadians:(id)json +{ + if ([json isKindOfClass:[NSString class]]) { + NSString *stringValue = (NSString *)json; + if ([stringValue hasSuffix:@"deg"]) { + CGFloat degrees = [[stringValue substringToIndex:stringValue.length - 3] floatValue]; + return degrees * M_PI / 180; + } + if ([stringValue hasSuffix:@"rad"]) { + return [[stringValue substringToIndex:stringValue.length - 3] floatValue]; + } + } + return [json floatValue]; +} + ++ (CATransform3D)CATransform3DFromMatrix:(id)json +{ + CATransform3D transform = CATransform3DIdentity; + if (!json) { + return transform; + } + if (![json isKindOfClass:[NSArray class]]) { + RCTLogConvertError(json, @"a CATransform3D. Expected array for transform matrix."); + return transform; + } + if ([json count] != kMatrixArrayLength) { + RCTLogConvertError(json, @"a CATransform3D. Expected 4x4 matrix array."); + return transform; + } + for (NSUInteger i = 0; i < kMatrixArrayLength; i++) { + ((CGFloat *)&transform)[i] = [RCTConvert CGFloat:json[i]]; + } + return transform; +} + ++ (CATransform3D)CATransform3D:(id)json +{ + CATransform3D transform = CATransform3DIdentity; + if (!json) { + return transform; + } + if (![json isKindOfClass:[NSArray class]]) { + RCTLogConvertError(json, @"a CATransform3D. Did you pass something other than an array?"); + return transform; + } + // legacy matrix support + if ([(NSArray *)json count] == kMatrixArrayLength && [json[0] isKindOfClass:[NSNumber class]]) { + RCTLogWarn(@"[RCTConvert CATransform3D:] has deprecated a matrix as input. Pass an array of configs (which can contain a matrix key) instead."); + return [self CATransform3DFromMatrix:json]; + } + for (NSDictionary *transformConfig in (NSArray *)json) { + if (transformConfig.count != 1) { + RCTLogConvertError(json, @"a CATransform3D. You must specify exactly one property per transform object."); + return transform; + } + NSString *property = transformConfig.allKeys[0]; + id value = transformConfig[property]; + + if ([property isEqualToString:@"matrix"]) { + transform = [self CATransform3DFromMatrix:value]; + + } else if ([property isEqualToString:@"perspective"]) { + transform.m34 = -1 / [value floatValue]; + + } else if ([property isEqualToString:@"rotateX"]) { + CGFloat rotate = [self convertToRadians:value]; + transform = CATransform3DRotate(transform, rotate, 1, 0, 0); + + } else if ([property isEqualToString:@"rotateY"]) { + CGFloat rotate = [self convertToRadians:value]; + transform = CATransform3DRotate(transform, rotate, 0, 1, 0); + + } else if ([property isEqualToString:@"rotate"] || [property isEqualToString:@"rotateZ"]) { + CGFloat rotate = [self convertToRadians:value]; + transform = CATransform3DRotate(transform, rotate, 0, 0, 1); + + } else if ([property isEqualToString:@"scale"]) { + CGFloat scale = [value floatValue]; + transform = CATransform3DScale(transform, scale, scale, 1); + + } else if ([property isEqualToString:@"scaleX"]) { + CGFloat scale = [value floatValue]; + transform = CATransform3DScale(transform, scale, 1, 1); + + } else if ([property isEqualToString:@"scaleY"]) { + CGFloat scale = [value floatValue]; + transform = CATransform3DScale(transform, 1, scale, 1); + + } else if ([property isEqualToString:@"translate"]) { + NSArray *array = (NSArray *)value; + CGFloat translateX = [array[0] floatValue]; + CGFloat translateY = [array[1] floatValue]; + CGFloat translateZ = array.count > 2 ? [array[2] floatValue] : 0; + transform = CATransform3DTranslate(transform, translateX, translateY, translateZ); + + } else if ([property isEqualToString:@"translateX"]) { + CGFloat translate = [value floatValue]; + transform = CATransform3DTranslate(transform, translate, 0, 0); + + } else if ([property isEqualToString:@"translateY"]) { + CGFloat translate = [value floatValue]; + transform = CATransform3DTranslate(transform, 0, translate, 0); + + } else if ([property isEqualToString:@"skewX"]) { + CGFloat skew = [self convertToRadians:value]; + transform.m21 = sinf(skew); + transform.m22 = cosf(skew); + + } else if ([property isEqualToString:@"skewY"]) { + CGFloat skew = [self convertToRadians:value]; + transform.m11 = cosf(skew); + transform.m12 = sinf(skew); + + } else { + RCTLogError(@"Unsupported transform type for a CATransform3D: %@.", property); + } + } + return transform; +} + +@end diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index cc1ac26b16d34e..cbecf8c13abefb 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -19,6 +19,7 @@ #import "RCTUtils.h" #import "RCTView.h" #import "UIView+React.h" +#import "RCTConvert+Transform.h" #if TARGET_OS_TV #import "RCTTVView.h"