More iOS animation fixes

Summary:
Main change is to the property diffing - we now use the last known props set on the view rather than the default props to compute the diff. This requires exposing a `getProps` method on all view components which should be fine I think.

I also realized that in more complex animations with multiple nodes, the node that the animation starts on might not be connected to a view, so we don't know if it's fabric just based on that, so we have to do a recursive search through the children to find if there are any that are associated with a fabric view to decide we should start the animation immediately. Unfortunately there can still be a timing gap here since the animated API is async and the uimanager API is sync - I'll need to change the animated API to be sync to completely fix this.

Reviewed By: shergin

Differential Revision: D14732028

fbshipit-source-id: 882c056b0b63aa576f8e42439be405cf7fb3147a
This commit is contained in:
Spencer Ahrens
2019-04-08 09:10:44 -07:00
committed by Facebook Github Bot
parent f04c039a98
commit 7b59c5a47e
13 changed files with 61 additions and 24 deletions

View File

@@ -23,6 +23,8 @@
@property (nonatomic, readonly) BOOL needsUpdate;
-(BOOL)isManagedByFabric;
/**
* Marks a node and its children as needing update.
*/

View File

@@ -115,4 +115,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
// during the current update loop
}
- (BOOL)isManagedByFabric
{
for (RCTAnimatedNode *child in _childNodes.objectEnumerator) {
if ([child isManagedByFabric]) {
return YES;
}
}
return NO;
}
@end

View File

@@ -36,6 +36,11 @@
return self;
}
- (BOOL)isManagedByFabric
{
return _managedByFabric;
}
- (void)connectToView:(NSNumber *)viewTag
viewName:(NSString *)viewName
bridge:(RCTBridge *)bridge

View File

@@ -19,7 +19,6 @@ typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager);
// Operations called before views have been updated.
NSMutableArray<AnimatedOperation> *_preOperations;
NSMutableDictionary<NSNumber *, NSNumber *> *_animIdIsManagedByFabric;
NSMutableDictionary<NSNumber *, NSNumber *> *_animatedNodeIsManagedByFabric;
}
RCT_EXPORT_MODULE();
@@ -88,7 +87,7 @@ RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager startAnimatingNode:animationId nodeTag:nodeTag config:config endCallback:callBack];
}];
if ([_animatedNodeIsManagedByFabric[nodeTag] boolValue]) {
if ([_nodesManager isNodeManagedByFabric:nodeTag]) {
_animIdIsManagedByFabric[animationId] = @YES;
[self flushOperationQueues];
}
@@ -138,9 +137,6 @@ RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
viewTag:(nonnull NSNumber *)viewTag)
{
NSString *viewName = [self.bridge.uiManager viewNameForReactTag:viewTag];
if (RCTUIManagerTypeForTagIsFabric(nodeTag)) {
_animatedNodeIsManagedByFabric[nodeTag] = @YES;
}
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag viewName:viewName];
}];

View File

@@ -19,6 +19,8 @@
- (void)stepAnimations:(nonnull CADisplayLink *)displaylink;
- (BOOL)isNodeManagedByFabric:(nonnull NSNumber *)tag;
// graph
- (void)createAnimatedNode:(nonnull NSNumber *)tag

View File

@@ -50,6 +50,12 @@
return self;
}
- (BOOL)isNodeManagedByFabric:(nonnull NSNumber *)tag
{
RCTAnimatedNode *node = _animationNodes[tag];
return [node isManagedByFabric];
}
#pragma mark -- Graph
- (void)createAnimatedNode:(nonnull NSNumber *)tag

View File

@@ -29,10 +29,14 @@ using namespace facebook::react;
static const auto defaultProps = std::make_shared<const ViewProps>();
_props = defaultProps;
}
return self;
}
- (facebook::react::SharedProps)props
{
return _props;
}
- (void)setContentView:(UIView *)contentView
{
if (_contentView) {

View File

@@ -85,6 +85,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)prepareForRecycle;
/**
* Read the last props used to update the view.
*/
- (facebook::react::SharedProps)props;
@end
NS_ASSUME_NONNULL_END

View File

@@ -9,6 +9,7 @@
#import <React/RCTMountingManagerDelegate.h>
#import <React/RCTPrimitives.h>
#import <react/core/ComponentDescriptor.h>
#import <react/core/ReactPrimitives.h>
#import <react/mounting/ShadowView.h>
#import <react/mounting/ShadowViewMutation.h>
@@ -40,8 +41,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)optimisticallyCreateComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle;
- (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag
oldProps:(facebook::react::SharedProps)oldProps
newProps:(facebook::react::SharedProps)newProps;
changedProps:(NSDictionary *)props
componentDescriptor:(const facebook::react::ComponentDescriptor &)componentDescriptor;
@end
NS_ASSUME_NONNULL_END

View File

@@ -8,8 +8,10 @@
#import "RCTMountingManager.h"
#import <React/RCTAssert.h>
#import <React/RCTFollyConvert.h>
#import <React/RCTUtils.h>
#import <react/core/LayoutableShadowNode.h>
#import <react/core/RawProps.h>
#import <react/debug/SystraceSection.h>
#import "RCTComponentViewProtocol.h"
@@ -40,7 +42,7 @@ using namespace facebook::react;
return self;
}
- (void)performTransactionWithMutations:(facebook::react::ShadowViewMutationList)mutations rootTag:(ReactTag)rootTag
- (void)performTransactionWithMutations:(ShadowViewMutationList)mutations rootTag:(ReactTag)rootTag
{
NSMutableArray<RCTMountItemProtocol> *mountItems;
@@ -193,9 +195,12 @@ using namespace facebook::react;
}
- (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag
oldProps:(SharedProps)oldProps
newProps:(SharedProps)newProps
changedProps:(NSDictionary *)props
componentDescriptor:(const ComponentDescriptor &)componentDescriptor
{
UIView<RCTComponentViewProtocol> *componentView = [self->_componentViewRegistry componentViewByTag:reactTag];
SharedProps oldProps = [componentView props];
SharedProps newProps = componentDescriptor.cloneProps(oldProps, RawProps(convertIdToFollyDynamic(props)));
RCTUpdatePropsMountItem *mountItem = [[RCTUpdatePropsMountItem alloc] initWithTag:reactTag
oldProps:oldProps
newProps:newProps];

View File

@@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)prepareForRecycle;
- (facebook::react::SharedProps)props;
@end
NS_ASSUME_NONNULL_END

View File

@@ -87,4 +87,10 @@ using namespace facebook::react;
// Default implementation does nothing.
}
- (facebook::react::SharedProps)props
{
RCTAssert(NO, @"props access should be implemented by RCTViewComponentView.");
return nullptr;
}
@end

View File

@@ -74,7 +74,7 @@ using namespace facebook::react;
} else {
_reactNativeConfig = std::make_shared<const EmptyReactNativeConfig>();
}
_observers = [NSMutableArray array];
[[NSNotificationCenter defaultCenter] addObserver:self
@@ -176,17 +176,9 @@ using namespace facebook::react;
}
ComponentHandle handle = [[componentView class] componentHandle];
const facebook::react::ComponentDescriptor &componentDescriptor = [self._scheduler getComponentDescriptor:handle];
// Note: we use an empty object for `oldProps` to rely on the diffing algorithm internal to the
// RCTComponentViewProtocol::updateProps method. If there is a bug in that diffing, some props
// could get reset. One way around this would be to require all RCTComponentViewProtocol
// implementations to expose their current props so we could clone them, but that could be
// problematic for threading and other reasons.
facebook::react::SharedProps newProps =
componentDescriptor.cloneProps(nullptr, RawProps(convertIdToFollyDynamic(props)));
facebook::react::SharedProps oldProps = componentDescriptor.cloneProps(nullptr, RawProps(folly::dynamic::object()));
[self->_mountingManager synchronouslyUpdateViewOnUIThread:tag oldProps:oldProps newProps:newProps];
[self->_mountingManager synchronouslyUpdateViewOnUIThread:tag
changedProps:props
componentDescriptor:componentDescriptor];
return YES;
}
@@ -225,7 +217,7 @@ using namespace facebook::react;
// Make sure initializeBridge completed
messageQueueThread->runOnQueueSync([] {});
}
auto runtime = (facebook::jsi::Runtime *)((RCTCxxBridge *)_batchedBridge).runtime;
RuntimeExecutor runtimeExecutor =