-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
REAUIManager.mm
380 lines (329 loc) · 14.5 KB
/
REAUIManager.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#import <Foundation/Foundation.h>
#import <RNReanimated/FeaturesConfig.h>
#import <RNReanimated/REAIOSScheduler.h>
#import <RNReanimated/REAUIManager.h>
#import <RNReanimated/Scheduler.h>
#import <React/RCTComponentData.h>
#import <React/RCTLayoutAnimation.h>
#import <React/RCTLayoutAnimationGroup.h>
#import <React/RCTModalHostView.h>
#import <React/RCTRootShadowView.h>
#import <React/RCTRootViewInternal.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#if __has_include(<RNScreens/RNSScreen.h>)
#import <RNScreens/RNSScreen.h>
#endif
@interface RCTUIManager (REA)
- (void)_manageChildren:(NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry;
- (void)_removeChildren:(NSArray<UIView *> *)children fromContainer:(UIView *)container;
- (void)_removeChildren:(NSArray<UIView *> *)children
fromContainer:(UIView *)container
withAnimation:(RCTLayoutAnimationGroup *)animation;
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView;
- (NSArray<id<RCTComponent>> *)_childrenToRemoveFromContainer:(id<RCTComponent>)container
atIndices:(NSArray<NSNumber *> *)atIndices;
@end
@implementation REAUIManager {
RCTLayoutAnimationGroup *_reactLayoutAnimationGroup;
NSMutableDictionary<NSNumber *, NSMutableSet<id<RCTComponent>> *> *_toBeRemovedRegister;
NSMutableDictionary<NSNumber *, NSNumber *> *_parentMapper;
REAAnimationsManager *_animationsManager;
std::weak_ptr<reanimated::Scheduler> _scheduler;
}
+ (NSString *)moduleName
{
return NSStringFromClass([RCTUIManager class]);
}
- (void)setBridge:(RCTBridge *)bridge
{
// setting a layout animation group with a deleting animation in order to
// allows us to call a different method in RCTUIManager for cleaning up exiting views
RCTLayoutAnimation *deletingAnimation = [[RCTLayoutAnimation alloc] initWithDuration:0 config:@{}];
_reactLayoutAnimationGroup = [[RCTLayoutAnimationGroup alloc] initWithCreatingLayoutAnimation:nil
updatingLayoutAnimation:nil
deletingLayoutAnimation:deletingAnimation
callback:nil];
if (!_blockSetter) {
_blockSetter = true;
self.bridge = bridge;
[super setValue:bridge forKey:@"_bridge"];
[self setValue:[bridge.uiManager valueForKey:@"_shadowViewRegistry"] forKey:@"_shadowViewRegistry"];
[self setValue:[bridge.uiManager valueForKey:@"_viewRegistry"] forKey:@"_viewRegistry"];
[self setValue:[bridge.uiManager valueForKey:@"_nativeIDRegistry"] forKey:@"_nativeIDRegistry"];
[self setValue:[bridge.uiManager valueForKey:@"_shadowViewsWithUpdatedProps"]
forKey:@"_shadowViewsWithUpdatedProps"];
[self setValue:[bridge.uiManager valueForKey:@"_shadowViewsWithUpdatedChildren"]
forKey:@"_shadowViewsWithUpdatedChildren"];
[self setValue:[bridge.uiManager valueForKey:@"_pendingUIBlocks"] forKey:@"_pendingUIBlocks"];
[self setValue:[bridge.uiManager valueForKey:@"_rootViewTags"] forKey:@"_rootViewTags"];
[self setValue:[bridge.uiManager valueForKey:@"_observerCoordinator"] forKey:@"_observerCoordinator"];
[self setValue:[bridge.uiManager valueForKey:@"_componentDataByName"] forKey:@"_componentDataByName"];
_blockSetter = false;
}
}
- (void)_removeChildren:(NSArray<UIView *> *)children
fromContainer:(UIView *)container
withAnimation:(RCTLayoutAnimationGroup *)animation
{
if (animation == _reactLayoutAnimationGroup) {
// if a removed view in this batch has an `exiting` animation,
// let REAAnimationsManager handle the removal
[_animationsManager removeChildren:children fromContainer:container];
} else {
// otherwise, if there's a layout animation group set,
// delegate to the React Native implementation for layout animations
[super _removeChildren:children fromContainer:container withAnimation:animation];
}
}
- (void)_manageChildren:(NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
{
if (!reanimated::FeaturesConfig::isLayoutAnimationEnabled()) {
[super _manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
addAtIndices:addAtIndices
removeAtIndices:removeAtIndices
registry:registry];
return;
}
// Reanimated changes /start
BOOL isUIViewRegistry = ((id)registry == (id)[self valueForKey:@"_viewRegistry"]);
if (isUIViewRegistry) {
BOOL wasProxyRemovalSet = [self valueForKey:@"_layoutAnimationGroup"] == _reactLayoutAnimationGroup;
BOOL wantProxyRemoval = NO;
if (!wasProxyRemovalSet) {
id<RCTComponent> container = registry[containerTag];
NSArray<id<RCTComponent>> *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container
atIndices:removeAtIndices];
for (UIView *removedChild in permanentlyRemovedChildren) {
if ([_animationsManager wantsHandleRemovalOfView:removedChild]) {
wantProxyRemoval = YES;
break;
}
[_animationsManager removeAnimationsFromSubtree:removedChild];
}
if (wantProxyRemoval) {
// set layout animation group
[super setNextLayoutAnimationGroup:_reactLayoutAnimationGroup];
}
}
}
// Reanimated changes /end
[super _manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
addAtIndices:addAtIndices
removeAtIndices:removeAtIndices
registry:registry];
}
- (void)callAnimationForTree:(UIView *)view parentTag:(NSNumber *)parentTag
{
_parentMapper[view.reactTag] = parentTag;
for (UIView *subView in view.reactSubviews) {
[self callAnimationForTree:subView parentTag:view.reactTag];
}
}
// Overrided https://github.com/facebook/react-native/blob/v0.65.0/React/Modules/RCTUIManager.m#L530
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView
{
if (!reanimated::FeaturesConfig::isLayoutAnimationEnabled()) {
return [super uiBlockWithLayoutUpdateForRootView:rootShadowView];
}
NSHashTable<RCTShadowView *> *affectedShadowViews = [NSHashTable weakObjectsHashTable];
[rootShadowView layoutWithAffectedShadowViews:affectedShadowViews];
if (!affectedShadowViews.count) {
// no frame change results in no UI update block
return nil;
}
typedef struct {
CGRect frame;
UIUserInterfaceLayoutDirection layoutDirection;
BOOL isNew;
BOOL parentIsNew;
RCTDisplayType displayType;
} RCTFrameData;
// Construct arrays then hand off to main thread
NSUInteger count = affectedShadowViews.count;
NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count];
NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count];
{
NSUInteger index = 0;
RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes;
for (RCTShadowView *shadowView in affectedShadowViews) {
reactTags[index] = shadowView.reactTag;
RCTLayoutMetrics layoutMetrics = shadowView.layoutMetrics;
frameDataArray[index++] = (RCTFrameData){
layoutMetrics.frame,
layoutMetrics.layoutDirection,
shadowView.isNewView,
shadowView.superview.isNewView,
layoutMetrics.displayType};
}
}
for (RCTShadowView *shadowView in affectedShadowViews) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
NSNumber *reactTag = shadowView.reactTag;
if (shadowView.onLayout) {
CGRect frame = shadowView.layoutMetrics.frame;
shadowView.onLayout(@{
@"layout" : @{
@"x" : @(frame.origin.x),
@"y" : @(frame.origin.y),
@"width" : @(frame.size.width),
@"height" : @(frame.size.height),
},
});
}
if (RCTIsReactRootView(reactTag) && [shadowView isKindOfClass:[RCTRootShadowView class]]) {
CGSize contentSize = shadowView.layoutMetrics.frame.size;
RCTExecuteOnMainQueue(^{
NSMutableDictionary<NSNumber *, UIView *> *viewRegistry = [self valueForKey:@"_viewRegistry"];
UIView *view = viewRegistry[reactTag];
RCTAssert(view != nil, @"view (for ID %@) not found", reactTag);
RCTRootView *rootView = (RCTRootView *)[view superview];
if ([rootView isKindOfClass:[RCTRootView class]]) {
rootView.intrinsicContentSize = contentSize;
}
});
}
}
// Perform layout (possibly animated)
return ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
const RCTFrameData *frameDataArray = (const RCTFrameData *)framesData.bytes;
RCTLayoutAnimationGroup *layoutAnimationGroup = [uiManager valueForKey:@"_layoutAnimationGroup"];
__block NSUInteger completionsCalled = 0;
NSInteger index = 0;
for (NSNumber *reactTag in reactTags) {
RCTFrameData frameData = frameDataArray[index++];
UIView *view = viewRegistry[reactTag];
CGRect frame = frameData.frame;
UIUserInterfaceLayoutDirection layoutDirection = frameData.layoutDirection;
BOOL isNew = frameData.isNew;
RCTLayoutAnimation *updatingLayoutAnimation = isNew ? nil : layoutAnimationGroup.updatingLayoutAnimation;
BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew;
RCTLayoutAnimation *creatingLayoutAnimation =
shouldAnimateCreation ? layoutAnimationGroup.creatingLayoutAnimation : nil;
BOOL isHidden = frameData.displayType == RCTDisplayTypeNone;
void (^completion)(BOOL) = ^(BOOL finished) {
completionsCalled++;
if (layoutAnimationGroup.callback && completionsCalled == count) {
layoutAnimationGroup.callback(@[ @(finished) ]);
// It's unsafe to call this callback more than once, so we nil it out here
// to make sure that doesn't happen.
layoutAnimationGroup.callback = nil;
}
};
if (view.reactLayoutDirection != layoutDirection) {
view.reactLayoutDirection = layoutDirection;
}
if (view.isHidden != isHidden) {
view.hidden = isHidden;
}
// Reanimated changes /start
REASnapshot *snapshotBefore = isNew ? nil : [self->_animationsManager prepareSnapshotBeforeMountForView:view];
// Reanimated changes /end
if (creatingLayoutAnimation) {
// Animate view creation
[view reactSetFrame:frame];
CATransform3D finalTransform = view.layer.transform;
CGFloat finalOpacity = view.layer.opacity;
NSString *property = creatingLayoutAnimation.property;
if ([property isEqualToString:@"scaleXY"]) {
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
} else if ([property isEqualToString:@"scaleX"]) {
view.layer.transform = CATransform3DMakeScale(0, 1, 0);
} else if ([property isEqualToString:@"scaleY"]) {
view.layer.transform = CATransform3DMakeScale(1, 0, 0);
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = 0.0;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@", creatingLayoutAnimation.property);
}
[creatingLayoutAnimation
performAnimations:^{
if ([property isEqualToString:@"scaleX"] || [property isEqualToString:@"scaleY"] ||
[property isEqualToString:@"scaleXY"]) {
view.layer.transform = finalTransform;
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = finalOpacity;
}
}
withCompletionBlock:completion];
} else if (updatingLayoutAnimation) {
// Animate view update
[updatingLayoutAnimation
performAnimations:^{
[view reactSetFrame:frame];
}
withCompletionBlock:completion];
} else {
// Update without animation
[view reactSetFrame:frame];
completion(YES);
}
// Reanimated changes /start
if (isNew || snapshotBefore != nil) {
[self->_animationsManager viewDidMount:view withBeforeSnapshot:snapshotBefore];
}
// Reanimated changes /end
}
// Clean up
// below line serves as this one uiManager->_layoutAnimationGroup = nil;, because we don't have access to the
// private field
[uiManager setNextLayoutAnimationGroup:nil];
};
}
- (Class)class
{
return [RCTUIManager class];
}
+ (Class)class
{
return [RCTUIManager class];
}
- (void)setUp:(REAAnimationsManager *)animationsManager
{
_animationsManager = animationsManager;
_toBeRemovedRegister = [[NSMutableDictionary<NSNumber *, NSMutableSet<id<RCTComponent>> *> alloc] init];
_parentMapper = [[NSMutableDictionary<NSNumber *, NSNumber *> alloc] init];
}
- (void)unregisterView:(id<RCTComponent>)view
{
NSNumber *tag = _parentMapper[view.reactTag];
if (tag == nil) {
return;
}
[_toBeRemovedRegister[tag] removeObject:view];
if (_toBeRemovedRegister[tag].count == 0) {
[_toBeRemovedRegister removeObjectForKey:tag];
}
NSMutableDictionary<NSNumber *, id<RCTComponent>> *viewRegistry = [self valueForKey:@"_viewRegistry"];
[view.reactSuperview removeReactSubview:view];
id<RCTComponent> parentView = viewRegistry[tag];
@try {
[parentView removeReactSubview:view];
} @catch (id anException) {
}
#if __has_include(<RNScreens/RNSScreen.h>)
if ([view isKindOfClass:[RNSScreenView class]]) {
[parentView didUpdateReactSubviews];
}
#endif
[viewRegistry removeObjectForKey:view.reactTag];
}
@end