Skip to content

Commit c9fae2f

Browse files
janicduplessisfacebook-github-bot
authored andcommitted
Native Animated - Restore default values when removing props on iOS
Summary: This fixes a bug that causes properties to keep stale values because they were not restored to their default after being removed when their value was controlled by native animated. To fix this we restore default values in `disconnectFromView` by updating views with null values for all props that we modified previously. However this causes another issue where we lose any props that were set by the normal process because NativeAnimated operations are always executed after UIManager operatations. To fix this I added a way to hook into UIManager view updating process to be able to execute NativeAnimated operations either before or after updating native views. In the case of disconnecting we want to do it before updating views so that it does: Value changed by native animated -> value restored to default -> (optional) value updated by normal prop. This PR also depends on #10658. **Test plan** Tested that this fixed a particular bug in an app that uses ex-navigation + native animations where a navbar w Closes #11819 Differential Revision: D4752566 Pulled By: javache fbshipit-source-id: 68ee28200ffeba859ae1b98ac753bd7dcb8910f0
1 parent acc1edd commit c9fae2f

File tree

5 files changed

+158
-38
lines changed

5 files changed

+158
-38
lines changed

Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m

+32-10
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,21 @@
1616
#import "RCTStyleAnimatedNode.h"
1717
#import "RCTValueAnimatedNode.h"
1818

19-
@implementation RCTPropsAnimatedNode {
19+
@implementation RCTPropsAnimatedNode
20+
{
2021
NSNumber *_connectedViewTag;
2122
NSString *_connectedViewName;
2223
RCTUIManager *_uiManager;
24+
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
25+
}
26+
27+
- (instancetype)initWithTag:(NSNumber *)tag
28+
config:(NSDictionary<NSString *, id> *)config;
29+
{
30+
if (self = [super initWithTag:tag config:config]) {
31+
_propsDictionary = [NSMutableDictionary new];
32+
}
33+
return self;
2334
}
2435

2536
- (void)connectToView:(NSNumber *)viewTag
@@ -33,6 +44,17 @@ - (void)connectToView:(NSNumber *)viewTag
3344

3445
- (void)disconnectFromView:(NSNumber *)viewTag
3546
{
47+
// Restore the default value for all props that were modified by this node.
48+
for (NSString *key in _propsDictionary.allKeys) {
49+
_propsDictionary[key] = [NSNull null];
50+
}
51+
52+
if (_propsDictionary.count) {
53+
[_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag
54+
viewName:_connectedViewName
55+
props:_propsDictionary];
56+
}
57+
3658
_connectedViewTag = nil;
3759
_connectedViewName = nil;
3860
_uiManager = nil;
@@ -41,7 +63,7 @@ - (void)disconnectFromView:(NSNumber *)viewTag
4163
- (NSString *)propertyNameForParentTag:(NSNumber *)parentTag
4264
{
4365
__block NSString *propertyName;
44-
[self.config[@"props"] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull property, NSNumber * _Nonnull tag, BOOL * _Nonnull stop) {
66+
[self.config[@"props"] enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull property, NSNumber *_Nonnull tag, BOOL *_Nonnull stop) {
4567
if ([tag isEqualToNumber:parentTag]) {
4668
propertyName = property;
4769
*stop = YES;
@@ -54,29 +76,29 @@ - (void)performUpdate
5476
{
5577
[super performUpdate];
5678

79+
// Since we are updating nodes after detaching them from views there is a time where it's
80+
// possible that the view was disconnected and still receive an update, this is normal and we can
81+
// simply skip that update.
5782
if (!_connectedViewTag) {
58-
RCTLogError(@"Node has not been attached to a view");
5983
return;
6084
}
6185

62-
NSMutableDictionary *props = [NSMutableDictionary dictionary];
63-
[self.parentNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull parentTag, RCTAnimatedNode * _Nonnull parentNode, BOOL * _Nonnull stop) {
86+
[self.parentNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull parentTag, RCTAnimatedNode *_Nonnull parentNode, BOOL *_Nonnull stop) {
6487

6588
if ([parentNode isKindOfClass:[RCTStyleAnimatedNode class]]) {
66-
[props addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
89+
[self->_propsDictionary addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
6790

6891
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
6992
NSString *property = [self propertyNameForParentTag:parentTag];
7093
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
71-
[props setObject:@(value) forKey:property];
94+
self->_propsDictionary[property] = @(value);
7295
}
73-
7496
}];
7597

76-
if (props.count) {
98+
if (_propsDictionary.count) {
7799
[_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag
78100
viewName:_connectedViewName
79-
props:props];
101+
props:_propsDictionary];
80102
}
81103
}
82104

Libraries/NativeAnimation/RCTNativeAnimatedModule.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
#import <React/RCTBridgeModule.h>
1111
#import <React/RCTEventDispatcher.h>
1212
#import <React/RCTEventEmitter.h>
13+
#import <React/RCTUIManager.h>
1314

1415
#import "RCTValueAnimatedNode.h"
1516

16-
@interface RCTNativeAnimatedModule : RCTEventEmitter <RCTBridgeModule, RCTValueAnimatedNodeObserver, RCTEventDispatcherObserver>
17+
@interface RCTNativeAnimatedModule : RCTEventEmitter <RCTBridgeModule, RCTValueAnimatedNodeObserver, RCTEventDispatcherObserver, RCTUIManagerObserver>
1718

1819
@end

Libraries/NativeAnimation/RCTNativeAnimatedModule.m

+57-27
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,27 @@
1515
@implementation RCTNativeAnimatedModule
1616
{
1717
RCTNativeAnimatedNodesManager *_nodesManager;
18+
19+
// Oparations called after views have been updated.
1820
NSMutableArray<AnimatedOperation> *_operations;
21+
// Operations called before views have been updated.
22+
NSMutableArray<AnimatedOperation> *_preOperations;
1923
}
2024

2125
RCT_EXPORT_MODULE();
2226

2327
- (void)invalidate
2428
{
2529
[_nodesManager stopAnimationLoop];
26-
}
27-
28-
- (void)dealloc
29-
{
3030
[self.bridge.eventDispatcher removeDispatchObserver:self];
31+
[self.bridge.uiManager removeUIManagerObserver:self];
3132
}
3233

3334
- (dispatch_queue_t)methodQueue
3435
{
36+
// This module needs to be on the same queue as the UIManager to avoid
37+
// having to lock `_operations` and `_preOperations` since `uiManagerWillFlushUIBlocks`
38+
// will be called from that queue.
3539
return RCTGetUIManagerQueue();
3640
}
3741

@@ -41,32 +45,34 @@ - (void)setBridge:(RCTBridge *)bridge
4145

4246
_nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithUIManager:self.bridge.uiManager];
4347
_operations = [NSMutableArray new];
48+
_preOperations = [NSMutableArray new];
4449

4550
[bridge.eventDispatcher addDispatchObserver:self];
51+
[bridge.uiManager addUIManagerObserver:self];
4652
}
4753

4854
#pragma mark -- API
4955

5056
RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag
5157
config:(NSDictionary<NSString *, id> *)config)
5258
{
53-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
59+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
5460
[nodesManager createAnimatedNode:tag config:config];
5561
}];
5662
}
5763

5864
RCT_EXPORT_METHOD(connectAnimatedNodes:(nonnull NSNumber *)parentTag
5965
childTag:(nonnull NSNumber *)childTag)
6066
{
61-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
67+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
6268
[nodesManager connectAnimatedNodes:parentTag childTag:childTag];
6369
}];
6470
}
6571

6672
RCT_EXPORT_METHOD(disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
6773
childTag:(nonnull NSNumber *)childTag)
6874
{
69-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
75+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
7076
[nodesManager disconnectAnimatedNodes:parentTag childTag:childTag];
7177
}];
7278
}
@@ -76,44 +82,44 @@ - (void)setBridge:(RCTBridge *)bridge
7682
config:(NSDictionary<NSString *, id> *)config
7783
endCallback:(RCTResponseSenderBlock)callBack)
7884
{
79-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
85+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
8086
[nodesManager startAnimatingNode:animationId nodeTag:nodeTag config:config endCallback:callBack];
8187
}];
8288
}
8389

8490
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
8591
{
86-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
92+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
8793
[nodesManager stopAnimation:animationId];
8894
}];
8995
}
9096

9197
RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
9298
value:(nonnull NSNumber *)value)
9399
{
94-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
100+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
95101
[nodesManager setAnimatedNodeValue:nodeTag value:value];
96102
}];
97103
}
98104

99105
RCT_EXPORT_METHOD(setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
100106
offset:(nonnull NSNumber *)offset)
101107
{
102-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
108+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
103109
[nodesManager setAnimatedNodeOffset:nodeTag offset:offset];
104110
}];
105111
}
106112

107113
RCT_EXPORT_METHOD(flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
108114
{
109-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
115+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
110116
[nodesManager flattenAnimatedNodeOffset:nodeTag];
111117
}];
112118
}
113119

114120
RCT_EXPORT_METHOD(extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
115121
{
116-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
122+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
117123
[nodesManager extractAnimatedNodeOffset:nodeTag];
118124
}];
119125
}
@@ -122,38 +128,41 @@ - (void)setBridge:(RCTBridge *)bridge
122128
viewTag:(nonnull NSNumber *)viewTag)
123129
{
124130
NSString *viewName = [self.bridge.uiManager viewNameForReactTag:viewTag];
125-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
131+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
126132
[nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag viewName:viewName];
127133
}];
128134
}
129135

130136
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
131137
viewTag:(nonnull NSNumber *)viewTag)
132138
{
133-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
139+
// Disconnecting a view also restores its default values so we have to make
140+
// sure this happens before views get updated with their new props. This is
141+
// why we enqueue this on the pre-operations queue.
142+
[self addPreOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
134143
[nodesManager disconnectAnimatedNodeFromView:nodeTag viewTag:viewTag];
135144
}];
136145
}
137146

138147
RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag)
139148
{
140-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
149+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
141150
[nodesManager dropAnimatedNode:tag];
142151
}];
143152
}
144153

145154
RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
146155
{
147156
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
148-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
157+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
149158
[nodesManager startListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
150159
}];
151160
}
152161

153162
RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
154163
{
155164
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
156-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
165+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
157166
[nodesManager stopListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
158167
}];
159168
}
@@ -162,7 +171,7 @@ - (void)setBridge:(RCTBridge *)bridge
162171
eventName:(nonnull NSString *)eventName
163172
eventMapping:(NSDictionary<NSString *, id> *)eventMapping)
164173
{
165-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
174+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
166175
[nodesManager addAnimatedEventToView:viewTag eventName:eventName eventMapping:eventMapping];
167176
}];
168177
}
@@ -171,24 +180,45 @@ - (void)setBridge:(RCTBridge *)bridge
171180
eventName:(nonnull NSString *)eventName
172181
animatedNodeTag:(nonnull NSNumber *)animatedNodeTag)
173182
{
174-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
183+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
175184
[nodesManager removeAnimatedEventFromView:viewTag eventName:eventName animatedNodeTag:animatedNodeTag];
176185
}];
177186
}
178187

179188
#pragma mark -- Batch handling
180189

181-
- (void)batchDidComplete
190+
- (void)addOperationBlock:(AnimatedOperation)operation
182191
{
183-
NSArray *operations = _operations;
192+
[_operations addObject:operation];
193+
}
194+
195+
- (void)addPreOperationBlock:(AnimatedOperation)operation
196+
{
197+
[_preOperations addObject:operation];
198+
}
199+
200+
- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)uiManager
201+
{
202+
if (_preOperations.count == 0 && _operations.count == 0) {
203+
return;
204+
}
205+
206+
NSArray<AnimatedOperation> *preOperations = _preOperations;
207+
NSArray<AnimatedOperation> *operations = _operations;
208+
_preOperations = [NSMutableArray new];
184209
_operations = [NSMutableArray new];
185210

186-
dispatch_async(dispatch_get_main_queue(), ^{
187-
[operations enumerateObjectsUsingBlock:^(AnimatedOperation operation, NSUInteger i, BOOL *stop) {
211+
[uiManager prependUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
212+
for (AnimatedOperation operation in preOperations) {
188213
operation(self->_nodesManager);
189-
}];
190-
[self->_nodesManager updateAnimations];
191-
});
214+
}
215+
}];
216+
217+
[uiManager addUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
218+
for (AnimatedOperation operation in operations) {
219+
operation(self->_nodesManager);
220+
}
221+
}];
192222
}
193223

194224
#pragma mark -- Events

React/Modules/RCTUIManager.h

+34
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ RCT_EXTERN NSString *const RCTUIManagerDidRemoveRootViewNotification;
4848
*/
4949
RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
5050

51+
@class RCTUIManager;
52+
53+
/**
54+
* Allows to hook into UIManager internals. This can be used to execute code at
55+
* specific points during the view updating process.
56+
*/
57+
@protocol RCTUIManagerObserver <NSObject>
58+
59+
/**
60+
* Called before flushing UI blocks at the end of a batch. Note that this won't
61+
* get called for partial batches when using `unsafeFlushUIChangesBeforeBatchEnds`.
62+
* This is called from the UIManager queue. Can be used to add UI operations in that batch.
63+
*/
64+
- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)manager;
65+
66+
@end
67+
5168
@protocol RCTScrollableProtocol;
5269

5370
/**
@@ -105,6 +122,23 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
105122
*/
106123
- (void)addUIBlock:(RCTViewManagerUIBlock)block;
107124

125+
/**
126+
* Schedule a block to be executed on the UI thread. Useful if you need to execute
127+
* view logic before all currently queued view updates have completed.
128+
*/
129+
- (void)prependUIBlock:(RCTViewManagerUIBlock)block;
130+
131+
/**
132+
* Add a UIManagerObserver. See the RCTUIManagerObserver protocol for more info. This
133+
* method can be called safely from any queue.
134+
*/
135+
- (void)addUIManagerObserver:(id<RCTUIManagerObserver>)observer;
136+
137+
/**
138+
* Remove a UIManagerObserver. This method can be called safely from any queue.
139+
*/
140+
- (void)removeUIManagerObserver:(id<RCTUIManagerObserver>)observer;
141+
108142
/**
109143
* Used by native animated module to bypass the process of updating the values through the shadow
110144
* view hierarchy. This method will directly update native views, which means that updates for

0 commit comments

Comments
 (0)