Skip to content

Commit c31d6ff

Browse files
Properly restore default values when removing props driven by native animated
1 parent abf75fa commit c31d6ff

File tree

5 files changed

+148
-31
lines changed

5 files changed

+148
-31
lines changed

Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m

+31-7
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,24 @@
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

36+
2537
- (void)connectToView:(NSNumber *)viewTag
2638
viewName:(NSString *)viewName
2739
uiManager:(RCTUIManager *)uiManager
@@ -33,6 +45,17 @@ - (void)connectToView:(NSNumber *)viewTag
3345

3446
- (void)disconnectFromView:(NSNumber *)viewTag
3547
{
48+
// Restore the default value for all props that were modified by this node.
49+
for (NSString *key in [_propsDictionary allKeys]) {
50+
[_propsDictionary setObject:[NSNull null] forKey:key];
51+
}
52+
53+
if (_propsDictionary.count) {
54+
[_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag
55+
viewName:_connectedViewName
56+
props:_propsDictionary];
57+
}
58+
3659
_connectedViewTag = nil;
3760
_connectedViewName = nil;
3861
_uiManager = nil;
@@ -54,29 +77,30 @@ - (void)performUpdate
5477
{
5578
[super performUpdate];
5679

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

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

6589
if ([parentNode isKindOfClass:[RCTStyleAnimatedNode class]]) {
66-
[props addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
90+
[_propsDictionary addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
6791

6892
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
6993
NSString *property = [self propertyNameForParentTag:parentTag];
7094
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
71-
[props setObject:@(value) forKey:property];
95+
[_propsDictionary setObject:@(value) forKey:property];
7296
}
7397

7498
}];
7599

76-
if (props.count) {
100+
if (_propsDictionary.count) {
77101
[_uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag
78102
viewName:_connectedViewName
79-
props:props];
103+
props:_propsDictionary];
80104
}
81105
}
82106

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

+62-23
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
@implementation RCTNativeAnimatedModule
1616
{
1717
RCTNativeAnimatedNodesManager *_nodesManager;
18+
// Oparations called after views have been updated.
1819
NSMutableArray<AnimatedOperation> *_operations;
20+
// Operations called before views have been updated.
21+
NSMutableArray<AnimatedOperation> *_preOperations;
22+
// Lock used for _operations and _preOperations. This is needed since operations
23+
// are added from the UIManager queue and executed from the main queue.
24+
NSLock *_operationsLock;
1925
}
2026

2127
RCT_EXPORT_MODULE();
@@ -28,6 +34,7 @@ - (void)invalidate
2834
- (void)dealloc
2935
{
3036
[self.bridge.eventDispatcher removeDispatchObserver:self];
37+
[self.bridge.uiManager removeUIManagerObserver:self];
3138
}
3239

3340
- (dispatch_queue_t)methodQueue
@@ -41,32 +48,35 @@ - (void)setBridge:(RCTBridge *)bridge
4148

4249
_nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithUIManager:self.bridge.uiManager];
4350
_operations = [NSMutableArray new];
51+
_preOperations = [NSMutableArray new];
52+
_operationsLock = [NSLock new];
4453

4554
[bridge.eventDispatcher addDispatchObserver:self];
55+
[bridge.uiManager addUIManagerObserver:self];
4656
}
4757

4858
#pragma mark -- API
4959

5060
RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag
5161
config:(NSDictionary<NSString *, id> *)config)
5262
{
53-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
63+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
5464
[nodesManager createAnimatedNode:tag config:config];
5565
}];
5666
}
5767

5868
RCT_EXPORT_METHOD(connectAnimatedNodes:(nonnull NSNumber *)parentTag
5969
childTag:(nonnull NSNumber *)childTag)
6070
{
61-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
71+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
6272
[nodesManager connectAnimatedNodes:parentTag childTag:childTag];
6373
}];
6474
}
6575

6676
RCT_EXPORT_METHOD(disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
6777
childTag:(nonnull NSNumber *)childTag)
6878
{
69-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
79+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
7080
[nodesManager disconnectAnimatedNodes:parentTag childTag:childTag];
7181
}];
7282
}
@@ -76,44 +86,44 @@ - (void)setBridge:(RCTBridge *)bridge
7686
config:(NSDictionary<NSString *, id> *)config
7787
endCallback:(RCTResponseSenderBlock)callBack)
7888
{
79-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
89+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
8090
[nodesManager startAnimatingNode:animationId nodeTag:nodeTag config:config endCallback:callBack];
8191
}];
8292
}
8393

8494
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
8595
{
86-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
96+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
8797
[nodesManager stopAnimation:animationId];
8898
}];
8999
}
90100

91101
RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
92102
value:(nonnull NSNumber *)value)
93103
{
94-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
104+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
95105
[nodesManager setAnimatedNodeValue:nodeTag value:value];
96106
}];
97107
}
98108

99109
RCT_EXPORT_METHOD(setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
100110
offset:(nonnull NSNumber *)offset)
101111
{
102-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
112+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
103113
[nodesManager setAnimatedNodeOffset:nodeTag offset:offset];
104114
}];
105115
}
106116

107117
RCT_EXPORT_METHOD(flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
108118
{
109-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
119+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
110120
[nodesManager flattenAnimatedNodeOffset:nodeTag];
111121
}];
112122
}
113123

114124
RCT_EXPORT_METHOD(extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
115125
{
116-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
126+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
117127
[nodesManager extractAnimatedNodeOffset:nodeTag];
118128
}];
119129
}
@@ -122,38 +132,41 @@ - (void)setBridge:(RCTBridge *)bridge
122132
viewTag:(nonnull NSNumber *)viewTag)
123133
{
124134
NSString *viewName = [self.bridge.uiManager viewNameForReactTag:viewTag];
125-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
135+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
126136
[nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag viewName:viewName];
127137
}];
128138
}
129139

130140
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
131141
viewTag:(nonnull NSNumber *)viewTag)
132142
{
133-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
143+
// Disconnecting a view also restores it's default values so we have to make
144+
// sure this happens before views get updated with their new props. This is
145+
// why we enqueue this on the pre-operations queue.
146+
[self addPreOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
134147
[nodesManager disconnectAnimatedNodeFromView:nodeTag viewTag:viewTag];
135148
}];
136149
}
137150

138151
RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag)
139152
{
140-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
153+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
141154
[nodesManager dropAnimatedNode:tag];
142155
}];
143156
}
144157

145158
RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
146159
{
147160
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
148-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
161+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
149162
[nodesManager startListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
150163
}];
151164
}
152165

153166
RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
154167
{
155168
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
156-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
169+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
157170
[nodesManager stopListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
158171
}];
159172
}
@@ -162,32 +175,58 @@ - (void)setBridge:(RCTBridge *)bridge
162175
eventName:(nonnull NSString *)eventName
163176
eventMapping:(NSDictionary<NSString *, id> *)eventMapping)
164177
{
165-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
178+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
166179
[nodesManager addAnimatedEventToView:viewTag eventName:eventName eventMapping:eventMapping];
167180
}];
168181
}
169182

170183
RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
171184
eventName:(nonnull NSString *)eventName)
172185
{
173-
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
186+
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
174187
[nodesManager removeAnimatedEventFromView:viewTag eventName:eventName];
175188
}];
176189
}
177190

178191
#pragma mark -- Batch handling
179192

180-
- (void)batchDidComplete
193+
- (void)addOperationBlock:(AnimatedOperation)operation
181194
{
195+
[_operationsLock lock];
196+
[_operations addObject:operation];
197+
[_operationsLock unlock];
198+
}
199+
200+
- (void)addPreOperationBlock:(AnimatedOperation)operation
201+
{
202+
[_operationsLock lock];
203+
[_preOperations addObject:operation];
204+
[_operationsLock unlock];
205+
}
206+
207+
- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)uiManager
208+
{
209+
[_operationsLock lock];
210+
NSArray *operations = _preOperations;
211+
_preOperations = [NSMutableArray new];
212+
[_operationsLock unlock];
213+
214+
[operations enumerateObjectsUsingBlock:^(AnimatedOperation operation, NSUInteger i, BOOL *stop) {
215+
operation(self->_nodesManager);
216+
}];
217+
}
218+
219+
- (void)uiManagerDidFlushUIBlocks:(RCTUIManager *)uiManager
220+
{
221+
[_operationsLock lock];
182222
NSArray *operations = _operations;
183223
_operations = [NSMutableArray new];
224+
[_operationsLock unlock];
184225

185-
dispatch_async(dispatch_get_main_queue(), ^{
186-
[operations enumerateObjectsUsingBlock:^(AnimatedOperation operation, NSUInteger i, BOOL *stop) {
187-
operation(self->_nodesManager);
188-
}];
189-
[self->_nodesManager updateAnimations];
190-
});
226+
[operations enumerateObjectsUsingBlock:^(AnimatedOperation operation, NSUInteger i, BOOL *stop) {
227+
operation(self->_nodesManager);
228+
}];
229+
[self->_nodesManager updateAnimations];
191230
}
192231

193232
#pragma mark -- Events

React/Modules/RCTUIManager.h

+32
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,28 @@ 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. This is called from
61+
* the main queue.
62+
*/
63+
- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)manager;
64+
65+
/**
66+
* Called after all UI blocks have been flushed at the end of a batch. This is
67+
* called from the main queue.
68+
*/
69+
- (void)uiManagerDidFlushUIBlocks:(RCTUIManager *)manager;
70+
71+
@end
72+
5173
@protocol RCTScrollableProtocol;
5274

5375
/**
@@ -95,6 +117,16 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
95117
*/
96118
- (void)addUIBlock:(RCTViewManagerUIBlock)block;
97119

120+
/**
121+
* Add a UIManagerObserver. See the RCTUIManagerObserver protocol for more info.
122+
*/
123+
- (void)addUIManagerObserver:(id<RCTUIManagerObserver>)observer;
124+
125+
/**
126+
* Remove a UIManagerObserver.
127+
*/
128+
- (void)removeUIManagerObserver:(id<RCTUIManagerObserver>)observer;
129+
98130
/**
99131
* Used by native animated module to bypass the process of updating the values through the shadow
100132
* view hierarchy. This method will directly update native views, which means that updates for

0 commit comments

Comments
 (0)