UNPKG

61.5 kBPlain TextView Raw
1/*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import "RCTUIManager.h"
9
10#import <AVFoundation/AVFoundation.h>
11
12#import <yoga/Yoga.h> // TODO(macOS ISS#2323203)
13
14#import "RCTAssert.h"
15#import "RCTBridge+Private.h"
16#import "RCTBridge.h"
17#import "RCTComponent.h"
18#import "RCTComponentData.h"
19#import "RCTConvert.h"
20#import "RCTDefines.h"
21#import "RCTDevSettings.h" // TODO(macOS ISS#2323203)
22#import "RCTEventDispatcher.h"
23#import "RCTLayoutAnimation.h"
24#import "RCTLayoutAnimationGroup.h"
25#import "RCTLog.h"
26#import "RCTModuleData.h"
27#import "RCTModuleMethod.h"
28#import "RCTProfile.h"
29#import "RCTRootContentView.h"
30#import "RCTRootShadowView.h"
31#import "RCTRootViewInternal.h"
32#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
33#import "RCTScrollableProtocol.h"
34#endif // TODO(macOS ISS#2323203)
35#import "RCTShadowView+Internal.h"
36#import "RCTShadowView.h"
37#import "RCTSurfaceRootShadowView.h"
38#import "RCTSurfaceRootView.h"
39#import "RCTUIManagerObserverCoordinator.h"
40#import "RCTUIManagerUtils.h"
41#import "RCTUtils.h"
42#import "RCTView.h"
43#import "RCTViewManager.h"
44#import "UIView+React.h"
45#import "RCTDeviceInfo.h" // TODO(macOS ISS#2323203)
46
47#import <React/RCTUIKit.h>
48
49void RCTTraverseViewNodes(id<RCTComponent> view, void (^block)(id<RCTComponent>)) // TODO(OSS Candidate ISS#2710739)
50{
51 if (view.reactTag) {
52 block(view);
53
54 for (id<RCTComponent> subview in view.reactSubviews) {
55 RCTTraverseViewNodes(subview, block);
56 }
57 }
58}
59
60static NSString *RCTNativeIDRegistryKey(NSString *nativeID, NSNumber *rootTag)
61{
62 if (!nativeID || !rootTag) {
63 return @"";
64 }
65 return [NSString stringWithFormat:@"%@-%@", rootTag, nativeID];
66}
67
68NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification =
69 @"RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification";
70
71@implementation RCTUIManager {
72 // Root views are only mutated on the shadow queue
73 NSMutableSet<NSNumber *> *_rootViewTags;
74 NSMutableArray<RCTViewManagerUIBlock> *_pendingUIBlocks;
75
76 // Animation
77 RCTLayoutAnimationGroup *_layoutAnimationGroup; // Main thread only
78
79 NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
80 NSMutableDictionary<NSNumber *, RCTPlatformView *> *_viewRegistry; // Main thread only // TODO(macOS ISS#2323203)
81 NSMapTable<NSString *, RCTPlatformView *> *_nativeIDRegistry; // TODO(macOS ISS#2323203)
82
83 NSMapTable<RCTShadowView *, NSArray<NSString *> *> *_shadowViewsWithUpdatedProps; // UIManager queue only.
84 NSHashTable<RCTShadowView *> *_shadowViewsWithUpdatedChildren; // UIManager queue only.
85
86 // Keyed by viewName
87 NSMutableDictionary *_componentDataByName;
88}
89
90@synthesize bridge = _bridge;
91
92RCT_EXPORT_MODULE()
93
94+ (BOOL)requiresMainQueueSetup
95{
96 return NO;
97}
98
99- (void)invalidate
100{
101 /**
102 * Called on the JS Thread since all modules are invalidated on the JS thread
103 */
104
105 // This only accessed from the shadow queue
106 _pendingUIBlocks = nil;
107
108 RCTExecuteOnMainQueue(^{
109 RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"UIManager invalidate", nil);
110 for (NSNumber *rootViewTag in self->_rootViewTags) {
111 RCTUIView *rootView = self->_viewRegistry[rootViewTag]; // TODO(macOS ISS#3536887)
112 if ([rootView conformsToProtocol:@protocol(RCTInvalidating)]) {
113 [(id<RCTInvalidating>)rootView invalidate];
114 }
115 }
116
117 self->_rootViewTags = nil;
118 self->_shadowViewRegistry = nil;
119 self->_viewRegistry = nil;
120 self->_nativeIDRegistry = nil;
121 self->_bridge = nil;
122
123 [[NSNotificationCenter defaultCenter] removeObserver:self];
124 RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
125 });
126}
127
128- (NSMutableDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
129{
130 // NOTE: this method only exists so that it can be accessed by unit tests
131 if (!_shadowViewRegistry) {
132 _shadowViewRegistry = [NSMutableDictionary new];
133 }
134 return _shadowViewRegistry;
135}
136
137- (NSMutableDictionary<NSNumber *, RCTPlatformView *> *)viewRegistry // TODO(macOS ISS#2323203)
138{
139 // NOTE: this method only exists so that it can be accessed by unit tests
140 if (!_viewRegistry) {
141 _viewRegistry = [NSMutableDictionary new];
142 }
143 return _viewRegistry;
144}
145
146- (NSMapTable *)nativeIDRegistry
147{
148 if (!_nativeIDRegistry) {
149 _nativeIDRegistry = [NSMapTable strongToWeakObjectsMapTable];
150 }
151 return _nativeIDRegistry;
152}
153
154- (void)setBridge:(RCTBridge *)bridge
155{
156 RCTAssert(_bridge == nil, @"Should not re-use same UIManager instance");
157 _bridge = bridge;
158
159 _shadowViewRegistry = [NSMutableDictionary new];
160 _viewRegistry = [NSMutableDictionary new];
161 _nativeIDRegistry = [NSMapTable strongToWeakObjectsMapTable];
162
163 _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable];
164 _shadowViewsWithUpdatedChildren = [NSHashTable weakObjectsHashTable];
165
166 // Internal resources
167 _pendingUIBlocks = [NSMutableArray new];
168 _rootViewTags = [NSMutableSet new];
169
170 _observerCoordinator = [RCTUIManagerObserverCoordinator new];
171
172 // Get view managers from bridge=
173 _componentDataByName = [NSMutableDictionary new];
174 for (Class moduleClass in _bridge.moduleClasses) {
175 if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
176 RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass bridge:_bridge];
177 _componentDataByName[componentData.name] = componentData;
178 }
179 }
180
181#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
182 // This dispatch_async avoids a deadlock while configuring native modules
183 dispatch_async(dispatch_get_main_queue(), ^{
184 [[NSNotificationCenter defaultCenter] addObserver:self
185 selector:@selector(didReceiveNewContentSizeMultiplier)
186 name:@"RCTAccessibilityManagerDidUpdateMultiplierNotification"
187 object:[self->_bridge moduleForName:@"AccessibilityManager"
188 lazilyLoadIfNecessary:YES]];
189 });
190#endif // TODO(macOS ISS#2323203)
191#if !TARGET_OS_TV && !TARGET_OS_OSX // TODO(macOS ISS#2323203)
192 [[NSNotificationCenter defaultCenter] addObserver:self
193 selector:@selector(namedOrientationDidChange)
194 name:UIDeviceOrientationDidChangeNotification
195 object:nil];
196#endif
197#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
198 [RCTLayoutAnimation initializeStatics];
199#endif // TODO(macOS ISS#2323203)
200}
201
202#pragma mark - Event emitting
203
204#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
205- (void)didReceiveNewContentSizeMultiplier
206{
207 // Report the event across the bridge.
208#pragma clang diagnostic push
209#pragma clang diagnostic ignored "-Wdeprecated-declarations"
210 id multiplier = [[self->_bridge moduleForName:@"AccessibilityManager"
211 lazilyLoadIfNecessary:YES] valueForKey:@"multiplier"];
212 if (multiplier) {
213 [_bridge.eventDispatcher sendDeviceEventWithName:@"didUpdateContentSizeMultiplier" body:multiplier];
214 }
215#pragma clang diagnostic pop
216
217 RCTExecuteOnUIManagerQueue(^{
218 [[NSNotificationCenter defaultCenter]
219 postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
220 object:self];
221 [self setNeedsLayout];
222 });
223}
224#endif // TODO(macOS ISS#2323203)
225
226#if !TARGET_OS_TV && !TARGET_OS_OSX // TODO(macOS ISS#2323203)
227// Names and coordinate system from html5 spec:
228// https://developer.mozilla.org/en-US/docs/Web/API/Screen.orientation
229// https://developer.mozilla.org/en-US/docs/Web/API/Screen.lockOrientation
230static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
231{
232 NSString *name;
233 NSNumber *degrees = @0;
234 BOOL isLandscape = NO;
235 switch (orientation) {
236 case UIDeviceOrientationPortrait:
237 name = @"portrait-primary";
238 break;
239 case UIDeviceOrientationPortraitUpsideDown:
240 name = @"portrait-secondary";
241 degrees = @180;
242 break;
243 case UIDeviceOrientationLandscapeRight:
244 name = @"landscape-primary";
245 degrees = @-90;
246 isLandscape = YES;
247 break;
248 case UIDeviceOrientationLandscapeLeft:
249 name = @"landscape-secondary";
250 degrees = @90;
251 isLandscape = YES;
252 break;
253 case UIDeviceOrientationFaceDown:
254 case UIDeviceOrientationFaceUp:
255 case UIDeviceOrientationUnknown:
256 // Unsupported
257 return nil;
258 }
259 return @{
260 @"name" : name,
261 @"rotationDegrees" : degrees,
262 @"isLandscape" : @(isLandscape),
263 };
264}
265
266- (void)namedOrientationDidChange
267{
268 NSDictionary *orientationEvent = deviceOrientationEventBody([UIDevice currentDevice].orientation);
269 if (!orientationEvent) {
270 return;
271 }
272
273#pragma clang diagnostic push
274#pragma clang diagnostic ignored "-Wdeprecated-declarations"
275 [_bridge.eventDispatcher sendDeviceEventWithName:@"namedOrientationDidChange" body:orientationEvent];
276#pragma clang diagnostic pop
277}
278#endif
279
280- (dispatch_queue_t)methodQueue
281{
282 return RCTGetUIManagerQueue();
283}
284
285- (void)registerRootViewTag:(NSNumber *)rootTag
286{
287 RCTAssertUIManagerQueue();
288
289 RCTAssert(RCTIsReactRootView(rootTag), @"Attempt to register rootTag (%@) which is not actually root tag.", rootTag);
290
291 RCTAssert(
292 ![_rootViewTags containsObject:rootTag],
293 @"Attempt to register rootTag (%@) which was already registered.",
294 rootTag);
295
296 [_rootViewTags addObject:rootTag];
297
298 // Registering root shadow view
299 RCTSurfaceRootShadowView *shadowView = [RCTSurfaceRootShadowView new];
300 shadowView.reactTag = rootTag;
301 _shadowViewRegistry[rootTag] = shadowView;
302
303 // Registering root view
304 RCTExecuteOnMainQueue(^{
305 RCTSurfaceRootView *rootView = [RCTSurfaceRootView new];
306 rootView.reactTag = rootTag;
307 self->_viewRegistry[rootTag] = rootView;
308 });
309}
310
311- (void)registerRootView:(RCTRootContentView *)rootView
312{
313 RCTAssertMainQueue();
314
315 NSNumber *reactTag = rootView.reactTag;
316 RCTAssert(RCTIsReactRootView(reactTag), @"View %@ with tag #%@ is not a root view", rootView, reactTag);
317
318 RCTPlatformView *existingView = _viewRegistry[reactTag]; // TODO(macOS ISS#2323203)
319 RCTAssert(
320 existingView == nil || existingView == rootView,
321 @"Expect all root views to have unique tag. Added %@ twice",
322 reactTag);
323
324 CGSize availableSize = rootView.availableSize;
325
326 // Register view
327 _viewRegistry[reactTag] = rootView;
328
329 // Register shadow view
330 RCTExecuteOnUIManagerQueue(^{
331 if (!self->_viewRegistry) {
332 return;
333 }
334
335 RCTRootShadowView *shadowView = [RCTRootShadowView new];
336 shadowView.availableSize = availableSize;
337 shadowView.reactTag = reactTag;
338 shadowView.viewName = NSStringFromClass([rootView class]);
339 self->_shadowViewRegistry[shadowView.reactTag] = shadowView;
340 [self->_rootViewTags addObject:reactTag];
341 });
342}
343
344- (NSString *)viewNameForReactTag:(NSNumber *)reactTag
345{
346 RCTAssertUIManagerQueue();
347 NSString *name = _shadowViewRegistry[reactTag].viewName;
348 if (name) {
349 return name;
350 }
351
352 __block RCTPlatformView *view; // TODO(macOS ISS#2323203)
353 RCTUnsafeExecuteOnMainQueueSync(^{
354 view = self->_viewRegistry[reactTag];
355 });
356
357#pragma clang diagnostic push
358#pragma clang diagnostic ignored "-Wundeclared-selector"
359
360 if ([view respondsToSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)]) {
361 return [view performSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)];
362 }
363
364#pragma clang diagnostic pop
365 return nil;
366}
367
368- (RCTPlatformView *)viewForReactTag:(NSNumber *)reactTag // TODO(macOS ISS#2323203)
369{
370 RCTAssertMainQueue();
371 return _viewRegistry[reactTag];
372}
373
374- (RCTShadowView *)shadowViewForReactTag:(NSNumber *)reactTag
375{
376#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
377 RCTAssertUIManagerQueue();
378#endif // TODO(macOS ISS#2323203)
379 return _shadowViewRegistry[reactTag];
380}
381
382- (void)_executeBlockWithShadowView:(void (^)(RCTShadowView *shadowView))block forTag:(NSNumber *)tag
383{
384 RCTAssertMainQueue();
385
386 RCTExecuteOnUIManagerQueue(^{
387 RCTShadowView *shadowView = self->_shadowViewRegistry[tag];
388
389 if (shadowView == nil) {
390 RCTLogInfo(
391 @"Could not locate shadow view with tag #%@, this is probably caused by a temporary inconsistency between native views and shadow views.",
392 tag);
393 return;
394 }
395
396 block(shadowView);
397 });
398}
399
400- (void)setAvailableSize:(CGSize)availableSize forRootView:(RCTUIView *)rootView // TODO(macOS ISS#3536887)
401{
402 RCTAssertMainQueue();
403 [self
404 _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
405 RCTAssert(
406 [shadowView isKindOfClass:[RCTRootShadowView class]], @"Located shadow view is actually not root view.");
407
408 RCTRootShadowView *rootShadowView = (RCTRootShadowView *)shadowView;
409
410 if (CGSizeEqualToSize(availableSize, rootShadowView.availableSize)) {
411 return;
412 }
413
414 rootShadowView.availableSize = availableSize;
415 [self setNeedsLayout];
416 }
417 forTag:rootView.reactTag];
418}
419
420- (void)setLocalData:(NSObject *)localData forView:(RCTUIView *)view // TODO(macOS ISS#3536887)
421{
422 RCTAssertMainQueue();
423 [self
424 _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
425 shadowView.localData = localData;
426 [self setNeedsLayout];
427 }
428 forTag:view.reactTag];
429}
430
431- (RCTUIView *)viewForNativeID:(NSString *)nativeID withRootTag:(NSNumber *)rootTag
432{
433 if (!nativeID || !rootTag) {
434 return nil;
435 }
436 RCTUIView *view; // TODO(macOS ISS#3536887)
437 @synchronized(self) {
438 view = [_nativeIDRegistry objectForKey:RCTNativeIDRegistryKey(nativeID, rootTag)];
439 }
440 return view;
441}
442
443- (void)setNativeID:(NSString *)nativeID forView:(RCTUIView *)view // TODO(macOS ISS#3536887)
444{
445 if (!nativeID || !view) {
446 return;
447 }
448 __weak RCTUIManager *weakSelf = self;
449 RCTExecuteOnUIManagerQueue(^{
450 NSNumber *rootTag = [weakSelf shadowViewForReactTag:view.reactTag].rootView.reactTag;
451 @synchronized(weakSelf) {
452 [weakSelf.nativeIDRegistry setObject:view forKey:RCTNativeIDRegistryKey(nativeID, rootTag)];
453 }
454 });
455}
456
457- (void)setSize:(CGSize)size forView:(RCTUIView *)view // TODO(macOS ISS#3536887)
458{
459 RCTAssertMainQueue();
460 [self
461 _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
462 if (CGSizeEqualToSize(size, shadowView.size)) {
463 return;
464 }
465
466 shadowView.size = size;
467 [self setNeedsLayout];
468 }
469 forTag:view.reactTag];
470}
471
472- (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize forView:(RCTUIView *)view // TODO(macOS ISS#3536887)
473{
474 RCTAssertMainQueue();
475 [self
476 _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
477 if (CGSizeEqualToSize(shadowView.intrinsicContentSize, intrinsicContentSize)) {
478 return;
479 }
480
481 shadowView.intrinsicContentSize = intrinsicContentSize;
482 [self setNeedsLayout];
483 }
484 forTag:view.reactTag];
485}
486
487/**
488 * Unregisters views from registries
489 */
490- (void)_purgeChildren:(NSArray<id<RCTComponent>> *)children
491 fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
492{
493 for (id<RCTComponent> child in children) {
494 RCTTraverseViewNodes(registry[child.reactTag], ^(id<RCTComponent> subview) {
495 RCTAssert(![subview isReactRootView], @"Root views should not be unregistered");
496 if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) {
497 [(id<RCTInvalidating>)subview invalidate];
498 }
499 [registry removeObjectForKey:subview.reactTag];
500 });
501 }
502}
503
504- (void)addUIBlock:(RCTViewManagerUIBlock)block
505{
506 RCTAssertUIManagerQueue();
507
508 if (!block || !_viewRegistry) {
509 return;
510 }
511
512 [_pendingUIBlocks addObject:block];
513}
514
515- (void)prependUIBlock:(RCTViewManagerUIBlock)block
516{
517 RCTAssertUIManagerQueue();
518
519 if (!block || !_viewRegistry) {
520 return;
521 }
522
523 [_pendingUIBlocks insertObject:block atIndex:0];
524}
525
526- (void)setNextLayoutAnimationGroup:(RCTLayoutAnimationGroup *)layoutAnimationGroup
527{
528 RCTAssertMainQueue();
529
530 if (_layoutAnimationGroup && ![_layoutAnimationGroup isEqual:layoutAnimationGroup]) {
531 RCTLogWarn(
532 @"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.",
533 [_layoutAnimationGroup description],
534 [layoutAnimationGroup description]);
535 }
536
537 _layoutAnimationGroup = layoutAnimationGroup;
538}
539
540- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView
541{
542 RCTAssertUIManagerQueue();
543
544 NSHashTable<RCTShadowView *> *affectedShadowViews = [NSHashTable weakObjectsHashTable];
545 [rootShadowView layoutWithAffectedShadowViews:affectedShadowViews];
546
547 if (!affectedShadowViews.count) {
548 // no frame change results in no UI update block
549 return nil;
550 }
551
552 typedef struct {
553 CGRect frame;
554 UIUserInterfaceLayoutDirection layoutDirection;
555 BOOL isNew;
556 BOOL parentIsNew;
557 RCTDisplayType displayType;
558 } RCTFrameData;
559
560 // Construct arrays then hand off to main thread
561 NSUInteger count = affectedShadowViews.count;
562 NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count];
563 NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count];
564 {
565 NSUInteger index = 0;
566 RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes;
567 for (RCTShadowView *shadowView in affectedShadowViews) {
568 reactTags[index] = shadowView.reactTag;
569 RCTLayoutMetrics layoutMetrics = shadowView.layoutMetrics;
570 frameDataArray[index++] = (RCTFrameData){layoutMetrics.frame,
571 layoutMetrics.layoutDirection,
572 shadowView.isNewView,
573 shadowView.superview.isNewView,
574 layoutMetrics.displayType};
575 }
576 }
577
578 for (RCTShadowView *shadowView in affectedShadowViews) {
579 // We have to do this after we build the parentsAreNew array.
580 shadowView.newView = NO;
581
582 NSNumber *reactTag = shadowView.reactTag;
583
584 if (shadowView.onLayout) {
585 CGRect frame = shadowView.layoutMetrics.frame;
586 shadowView.onLayout(@{
587 @"layout" : @{
588 @"x" : @(frame.origin.x),
589 @"y" : @(frame.origin.y),
590 @"width" : @(frame.size.width),
591 @"height" : @(frame.size.height),
592 },
593 });
594 }
595
596 if (RCTIsReactRootView(reactTag) && [shadowView isKindOfClass:[RCTRootShadowView class]]) {
597 CGSize contentSize = shadowView.layoutMetrics.frame.size;
598
599 RCTExecuteOnMainQueue(^{
600 RCTPlatformView *view = self->_viewRegistry[reactTag]; // TODO(macOS ISS#2323203)
601 RCTAssert(view != nil, @"view (for ID %@) not found", reactTag);
602
603 RCTRootView *rootView = (RCTRootView *)[view superview];
604 if ([rootView isKindOfClass:[RCTRootView class]]) {
605 rootView.intrinsicContentSize = contentSize;
606 }
607 });
608 }
609 }
610
611 // Perform layout (possibly animated)
612 return ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
613 const RCTFrameData *frameDataArray = (const RCTFrameData *)framesData.bytes;
614 RCTLayoutAnimationGroup *layoutAnimationGroup = uiManager->_layoutAnimationGroup;
615
616 __block NSUInteger completionsCalled = 0;
617
618 NSInteger index = 0;
619 for (NSNumber *reactTag in reactTags) {
620 RCTFrameData frameData = frameDataArray[index++];
621
622 RCTPlatformView *view = viewRegistry[reactTag]; // TODO(macOS ISS#2323203)
623 CGRect frame = frameData.frame;
624
625 UIUserInterfaceLayoutDirection layoutDirection = frameData.layoutDirection;
626 BOOL isNew = frameData.isNew;
627 RCTLayoutAnimation *updatingLayoutAnimation = isNew ? nil : layoutAnimationGroup.updatingLayoutAnimation;
628 BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew;
629 RCTLayoutAnimation *creatingLayoutAnimation =
630 shouldAnimateCreation ? layoutAnimationGroup.creatingLayoutAnimation : nil;
631 BOOL isHidden = frameData.displayType == RCTDisplayTypeNone;
632
633 void (^completion)(BOOL) = ^(BOOL finished) {
634 completionsCalled++;
635 if (layoutAnimationGroup.callback && completionsCalled == count) {
636 layoutAnimationGroup.callback(@[ @(finished) ]);
637
638 // It's unsafe to call this callback more than once, so we nil it out here
639 // to make sure that doesn't happen.
640 layoutAnimationGroup.callback = nil;
641 }
642 };
643
644 if (view.reactLayoutDirection != layoutDirection) {
645 view.reactLayoutDirection = layoutDirection;
646 }
647
648 if (view.isHidden != isHidden) {
649 view.hidden = isHidden;
650 }
651
652 if (creatingLayoutAnimation) {
653 // Animate view creation
654 [view reactSetFrame:frame];
655
656 CATransform3D finalTransform = view.layer.transform;
657 CGFloat finalOpacity = view.layer.opacity;
658
659 NSString *property = creatingLayoutAnimation.property;
660 if ([property isEqualToString:@"scaleXY"]) {
661 view.layer.transform = CATransform3DMakeScale(0, 0, 0);
662 } else if ([property isEqualToString:@"scaleX"]) {
663 view.layer.transform = CATransform3DMakeScale(0, 1, 0);
664 } else if ([property isEqualToString:@"scaleY"]) {
665 view.layer.transform = CATransform3DMakeScale(1, 0, 0);
666 } else if ([property isEqualToString:@"opacity"]) {
667 view.layer.opacity = 0.0;
668 } else {
669 RCTLogError(@"Unsupported layout animation createConfig property %@", creatingLayoutAnimation.property);
670 }
671
672 [creatingLayoutAnimation
673 performAnimations:^{
674 if ([property isEqualToString:@"scaleX"] || [property isEqualToString:@"scaleY"] ||
675 [property isEqualToString:@"scaleXY"]) {
676 view.layer.transform = finalTransform;
677 } else if ([property isEqualToString:@"opacity"]) {
678 view.layer.opacity = finalOpacity;
679 }
680 }
681 withCompletionBlock:completion];
682
683 } else if (updatingLayoutAnimation) {
684 // Animate view update
685 [updatingLayoutAnimation
686 performAnimations:^{
687 [view reactSetFrame:frame];
688 }
689 withCompletionBlock:completion];
690
691 } else {
692 // Update without animation
693 [view reactSetFrame:frame];
694 completion(YES);
695 }
696 }
697
698 // Clean up
699 uiManager->_layoutAnimationGroup = nil;
700 };
701}
702
703/**
704 * A method to be called from JS, which takes a container ID and then releases
705 * all subviews for that container upon receipt.
706 */
707RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID : (nonnull NSNumber *)containerID)
708{
709 RCTLogWarn(
710 @"RCTUIManager.removeSubviewsFromContainerWithID method is deprecated and it will not be implemented in newer versions of RN (Fabric) - T47686450");
711 id<RCTComponent> container = _shadowViewRegistry[containerID];
712 RCTAssert(container != nil, @"container view (for ID %@) not found", containerID);
713
714 NSUInteger subviewsCount = [container reactSubviews].count;
715 NSMutableArray<NSNumber *> *indices = [[NSMutableArray alloc] initWithCapacity:subviewsCount];
716 for (NSUInteger childIndex = 0; childIndex < subviewsCount; childIndex++) {
717 [indices addObject:@(childIndex)];
718 }
719
720 [self manageChildren:containerID
721 moveFromIndices:nil
722 moveToIndices:nil
723 addChildReactTags:nil
724 addAtIndices:nil
725 removeAtIndices:indices];
726}
727
728/**
729 * Disassociates children from container. Doesn't remove from registries.
730 * TODO: use [NSArray getObjects:buffer] to reuse same fast buffer each time.
731 *
732 * @returns Array of removed items.
733 */
734- (NSArray<id<RCTComponent>> *)_childrenToRemoveFromContainer:(id<RCTComponent>)container
735 atIndices:(NSArray<NSNumber *> *)atIndices
736{
737 // If there are no indices to move or the container has no subviews don't bother
738 // We support parents with nil subviews so long as they're all nil so this allows for this behavior
739 if (atIndices.count == 0 || [container reactSubviews].count == 0) {
740 return nil;
741 }
742 // Construction of removed children must be done "up front", before indices are disturbed by removals.
743 NSMutableArray<id<RCTComponent>> *removedChildren = [NSMutableArray arrayWithCapacity:atIndices.count];
744 RCTAssert(container != nil, @"container view (for ID %@) not found", container);
745 for (NSNumber *indexNumber in atIndices) {
746 NSUInteger index = indexNumber.unsignedIntegerValue;
747 if (index < [container reactSubviews].count) {
748 [removedChildren addObject:[container reactSubviews][index]];
749 }
750 }
751 if (removedChildren.count != atIndices.count) {
752 NSString *message = [NSString stringWithFormat:@"removedChildren count (%tu) was not what we expected (%tu)",
753 removedChildren.count,
754 atIndices.count];
755 RCTFatal(RCTErrorWithMessage(message));
756 }
757 return removedChildren;
758}
759
760- (void)_removeChildren:(NSArray<id<RCTComponent>> *)children fromContainer:(id<RCTComponent>)container
761{
762 for (id<RCTComponent> removedChild in children) {
763 [container removeReactSubview:removedChild];
764 }
765}
766
767/**
768 * Remove subviews from their parent with an animation.
769 */
770- (void)_removeChildren:(NSArray<RCTPlatformView *> *)children // TODO(macOS ISS#2323203)
771 fromContainer:(RCTPlatformView *)container // TODO(macOS ISS#2323203)
772 withAnimation:(RCTLayoutAnimationGroup *)animation
773{
774 RCTAssertMainQueue();
775 RCTLayoutAnimation *deletingLayoutAnimation = animation.deletingLayoutAnimation;
776
777 __block NSUInteger completionsCalled = 0;
778 for (RCTPlatformView *removedChild in children) { // TODO(macOS ISS#2323203)
779 void (^completion)(BOOL) = ^(BOOL finished) {
780 completionsCalled++;
781
782 [removedChild removeFromSuperview];
783
784 if (animation.callback && completionsCalled == children.count) {
785 animation.callback(@[ @(finished) ]);
786
787 // It's unsafe to call this callback more than once, so we nil it out here
788 // to make sure that doesn't happen.
789 animation.callback = nil;
790 }
791 };
792
793 // Hack: At this moment we have two contradict intents.
794 // First one: We want to delete the view from view hierarchy.
795 // Second one: We want to animate this view, which implies the existence of this view in the hierarchy.
796 // So, we have to remove this view from React's view hierarchy but postpone removing from UIKit's hierarchy.
797 // Here the problem: the default implementation of `-[UIView removeReactSubview:]` also removes the view from
798 // UIKit's hierarchy. So, let's temporary restore the view back after removing. To do so, we have to memorize
799 // original `superview` (which can differ from `container`) and an index of removed view.
800 RCTPlatformView *originalSuperview = removedChild.superview; // TODO(macOS ISS#2323203)
801 NSUInteger originalIndex = [originalSuperview.subviews indexOfObjectIdenticalTo:removedChild];
802#if TARGET_OS_OSX // [TODO(macOS ISS#2323203)
803 NSView *nextLowerView = nil;
804 if (originalIndex > 0) {
805 nextLowerView = [originalSuperview.subviews objectAtIndex:originalIndex - 1];
806 }
807#endif // ]TODO(macOS ISS#2323203)
808 [container removeReactSubview:removedChild];
809 // Disable user interaction while the view is animating
810 // since the view is (conceptually) deleted and not supposed to be interactive.
811 if ([removedChild respondsToSelector:@selector(setUserInteractionEnabled:)]) { // [TODO(macOS ISS#2323203)
812 ((RCTUIView *)removedChild).userInteractionEnabled = NO; // TODO(macOS ISS#3536887)
813 }
814#if !TARGET_OS_OSX // ]TODO(macOS ISS#2323203)
815 [originalSuperview insertSubview:removedChild atIndex:originalIndex];
816#else // [TODO(macOS ISS#2323203)
817 [originalSuperview addSubview:removedChild positioned:nextLowerView == nil ? NSWindowBelow : NSWindowAbove relativeTo:nextLowerView];
818#endif // ]TODO(macOS ISS#2323203)
819
820 NSString *property = deletingLayoutAnimation.property;
821 [deletingLayoutAnimation
822 performAnimations:^{
823 if ([property isEqualToString:@"scaleXY"]) {
824 removedChild.layer.transform = CATransform3DMakeScale(0.001, 0.001, 0.001);
825 } else if ([property isEqualToString:@"scaleX"]) {
826 removedChild.layer.transform = CATransform3DMakeScale(0.001, 1, 0.001);
827 } else if ([property isEqualToString:@"scaleY"]) {
828 removedChild.layer.transform = CATransform3DMakeScale(1, 0.001, 0.001);
829 } else if ([property isEqualToString:@"opacity"]) {
830 removedChild.layer.opacity = 0.0;
831 } else {
832 RCTLogError(@"Unsupported layout animation createConfig property %@", deletingLayoutAnimation.property);
833 }
834 }
835 withCompletionBlock:completion];
836 }
837}
838
839RCT_EXPORT_METHOD(removeRootView : (nonnull NSNumber *)rootReactTag)
840{
841 RCTShadowView *rootShadowView = _shadowViewRegistry[rootReactTag];
842 RCTAssert(rootShadowView.superview == nil, @"root view cannot have superview (ID %@)", rootReactTag);
843 [self _purgeChildren:(NSArray<id<RCTComponent>> *)rootShadowView.reactSubviews
844 fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];
845 [_shadowViewRegistry removeObjectForKey:rootReactTag];
846 [_rootViewTags removeObject:rootReactTag];
847
848 [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
849 RCTAssertMainQueue();
850 RCTPlatformView *rootView = viewRegistry[rootReactTag]; // TODO(macOS ISS#2323203)
851 [uiManager _purgeChildren:(NSArray<id<RCTComponent>> *)rootView.reactSubviews
852 fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry];
853 [(NSMutableDictionary *)viewRegistry removeObjectForKey:rootReactTag];
854 }];
855}
856
857RCT_EXPORT_METHOD(replaceExistingNonRootView : (nonnull NSNumber *)reactTag withView : (nonnull NSNumber *)newReactTag)
858{
859 RCTLogWarn(
860 @"RCTUIManager.replaceExistingNonRootView method is deprecated and it will not be implemented in newer versions of RN (Fabric) - T47686450");
861 RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
862 RCTAssert(shadowView != nil, @"shadowView (for ID %@) not found", reactTag);
863
864 RCTShadowView *superShadowView = shadowView.superview;
865 if (!superShadowView) {
866 RCTAssert(NO, @"shadowView super (of ID %@) not found", reactTag);
867 return;
868 }
869
870 NSUInteger indexOfView = [superShadowView.reactSubviews indexOfObjectIdenticalTo:shadowView];
871 RCTAssert(indexOfView != NSNotFound, @"View's superview doesn't claim it as subview (id %@)", reactTag);
872 NSArray<NSNumber *> *removeAtIndices = @[ @(indexOfView) ];
873 NSArray<NSNumber *> *addTags = @[ newReactTag ];
874 [self manageChildren:superShadowView.reactTag
875 moveFromIndices:nil
876 moveToIndices:nil
877 addChildReactTags:addTags
878 addAtIndices:removeAtIndices
879 removeAtIndices:removeAtIndices];
880}
881
882RCT_EXPORT_METHOD(setChildren : (nonnull NSNumber *)containerTag reactTags : (NSArray<NSNumber *> *)reactTags)
883{
884 RCTSetChildren(containerTag, reactTags, (NSDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry);
885
886 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
887 RCTSetChildren(containerTag, reactTags, (NSDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry);
888 }];
889
890 [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]];
891}
892
893static void RCTSetChildren(
894 NSNumber *containerTag,
895 NSArray<NSNumber *> *reactTags,
896 NSDictionary<NSNumber *, id<RCTComponent>> *registry)
897{
898 id<RCTComponent> container = registry[containerTag];
899 NSInteger index = 0;
900 for (NSNumber *reactTag in reactTags) {
901 id<RCTComponent> view = registry[reactTag];
902 if (view) {
903 [container insertReactSubview:view atIndex:index++];
904 }
905 }
906}
907
908RCT_EXPORT_METHOD(manageChildren
909 : (nonnull NSNumber *)containerTag moveFromIndices
910 : (NSArray<NSNumber *> *)moveFromIndices moveToIndices
911 : (NSArray<NSNumber *> *)moveToIndices addChildReactTags
912 : (NSArray<NSNumber *> *)addChildReactTags addAtIndices
913 : (NSArray<NSNumber *> *)addAtIndices removeAtIndices
914 : (NSArray<NSNumber *> *)removeAtIndices)
915{
916 [self _manageChildren:containerTag
917 moveFromIndices:moveFromIndices
918 moveToIndices:moveToIndices
919 addChildReactTags:addChildReactTags
920 addAtIndices:addAtIndices
921 removeAtIndices:removeAtIndices
922 registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];
923
924 [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
925 [uiManager _manageChildren:containerTag
926 moveFromIndices:moveFromIndices
927 moveToIndices:moveToIndices
928 addChildReactTags:addChildReactTags
929 addAtIndices:addAtIndices
930 removeAtIndices:removeAtIndices
931 registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry];
932 }];
933
934 [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]];
935}
936
937- (void)_manageChildren:(NSNumber *)containerTag
938 moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
939 moveToIndices:(NSArray<NSNumber *> *)moveToIndices
940 addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
941 addAtIndices:(NSArray<NSNumber *> *)addAtIndices
942 removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
943 registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
944{
945 id<RCTComponent> container = registry[containerTag];
946 RCTAssert(
947 moveFromIndices.count == moveToIndices.count,
948 @"moveFromIndices had size %tu, moveToIndices had size %tu",
949 moveFromIndices.count,
950 moveToIndices.count);
951 RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add");
952
953 // Removes (both permanent and temporary moves) are using "before" indices
954 NSArray<id<RCTComponent>> *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container
955 atIndices:removeAtIndices];
956 NSArray<id<RCTComponent>> *temporarilyRemovedChildren = [self _childrenToRemoveFromContainer:container
957 atIndices:moveFromIndices];
958
959 BOOL isUIViewRegistry = ((id)registry == (id)_viewRegistry);
960 if (isUIViewRegistry && _layoutAnimationGroup.deletingLayoutAnimation) {
961 [self _removeChildren:(NSArray<RCTPlatformView *> *)permanentlyRemovedChildren // TODO(macOS ISS#2323203)
962 fromContainer:(RCTPlatformView *)container // TODO(macOS ISS#2323203)
963 withAnimation:_layoutAnimationGroup];
964 } else {
965 [self _removeChildren:permanentlyRemovedChildren fromContainer:container];
966 }
967
968 [self _removeChildren:temporarilyRemovedChildren fromContainer:container];
969 [self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
970
971 // Figure out what to insert - merge temporary inserts and adds
972 NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
973 for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
974 destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
975 }
976
977 for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
978 id<RCTComponent> view = registry[addChildReactTags[index]];
979 if (view) {
980 destinationsToChildrenToAdd[addAtIndices[index]] = view;
981 }
982 }
983
984 NSArray<NSNumber *> *sortedIndices =
985 [destinationsToChildrenToAdd.allKeys sortedArrayUsingSelector:@selector(compare:)];
986 for (NSNumber *reactIndex in sortedIndices) {
987 [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue];
988 }
989}
990
991RCT_EXPORT_METHOD(createView
992 : (nonnull NSNumber *)reactTag viewName
993 : (NSString *)viewName rootTag
994 : (nonnull NSNumber *)rootTag props
995 : (NSDictionary *)props)
996{
997 RCTComponentData *componentData = _componentDataByName[viewName];
998 if (componentData == nil) {
999 RCTLogError(@"No component found for view with name \"%@\"", viewName);
1000 }
1001
1002 // Register shadow view
1003 RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];
1004 if (shadowView) {
1005 [componentData setProps:props forShadowView:shadowView];
1006 _shadowViewRegistry[reactTag] = shadowView;
1007 RCTShadowView *rootView = _shadowViewRegistry[rootTag];
1008 RCTAssert(
1009 [rootView isKindOfClass:[RCTRootShadowView class]]
1010 || [rootView isKindOfClass:[RCTSurfaceRootShadowView class]]
1011 ,
1012 @"Given `rootTag` (%@) does not correspond to a valid root shadow view instance.",
1013 rootTag);
1014 shadowView.rootView = (RCTRootShadowView *)rootView;
1015 }
1016
1017 // Dispatch view creation directly to the main thread instead of adding to
1018 // UIBlocks array. This way, it doesn't get deferred until after layout.
1019 __block RCTPlatformView *preliminaryCreatedView = nil; // TODO(macOS ISS#2323203)
1020
1021 void (^createViewBlock)(void) = ^{
1022 // Do nothing on the second run.
1023 if (preliminaryCreatedView) {
1024 return;
1025 }
1026
1027 preliminaryCreatedView = [componentData createViewWithTag:reactTag rootTag:rootTag];
1028
1029 if (preliminaryCreatedView) {
1030 self->_viewRegistry[reactTag] = preliminaryCreatedView;
1031 }
1032 };
1033
1034 // We cannot guarantee that asynchronously scheduled block will be executed
1035 // *before* a block is added to the regular mounting process (simply because
1036 // mounting process can be managed externally while the main queue is
1037 // locked).
1038 // So, we positively dispatch it asynchronously and double check inside
1039 // the regular mounting block.
1040
1041 RCTExecuteOnMainQueue(createViewBlock);
1042
1043 [self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1044 createViewBlock();
1045
1046 if (preliminaryCreatedView) {
1047 [componentData setProps:props forView:preliminaryCreatedView];
1048 }
1049 }];
1050
1051 [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]];
1052}
1053
1054RCT_EXPORT_METHOD(updateView
1055 : (nonnull NSNumber *)reactTag viewName
1056 : (NSString *)viewName // not always reliable, use shadowView.viewName if available
1057 props
1058 : (NSDictionary *)props)
1059{
1060 RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
1061 RCTComponentData *componentData = _componentDataByName[shadowView.viewName ?: viewName];
1062 [componentData setProps:props forShadowView:shadowView];
1063
1064 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1065 RCTPlatformView *view = viewRegistry[reactTag]; // TODO(macOS ISS#2323203)
1066 [componentData setProps:props forView:view];
1067 }];
1068
1069 [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]];
1070}
1071
1072- (void)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag viewName:(NSString *)viewName props:(NSDictionary *)props
1073{
1074 RCTAssertMainQueue();
1075 RCTComponentData *componentData = _componentDataByName[viewName];
1076 RCTUIView *view = _viewRegistry[reactTag]; // TODO(macOS ISS#3536887)
1077 [componentData setProps:props forView:view];
1078}
1079
1080RCT_EXPORT_METHOD(focus : (nonnull NSNumber *)reactTag)
1081{
1082 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTUIView *> *viewRegistry) { // TODO(macOS ISS#3536887)
1083 RCTUIView *newResponder = viewRegistry[reactTag]; // TODO(macOS ISS#3536887)
1084 [newResponder reactFocus];
1085 }];
1086}
1087
1088RCT_EXPORT_METHOD(blur : (nonnull NSNumber *)reactTag)
1089{
1090 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTUIView *> *viewRegistry) { // TODO(macOS ISS#3536887)
1091 RCTUIView *currentResponder = viewRegistry[reactTag]; // TODO(macOS ISS#3536887)
1092 [currentResponder reactBlur];
1093 }];
1094}
1095
1096RCT_EXPORT_METHOD(findSubviewIn
1097 : (nonnull NSNumber *)reactTag atPoint
1098 : (CGPoint)point callback
1099 : (RCTResponseSenderBlock)callback)
1100{
1101 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1102 RCTPlatformView *view = viewRegistry[reactTag]; // TODO(macOS ISS#2323203)
1103 RCTPlatformView *target = RCTUIViewHitTestWithEvent(view, point, nil); // TODO(macOS ISS#2323203) and TODO(macOS ISS#3536887)
1104 CGRect frame = [target convertRect:target.bounds toView:view];
1105
1106 while (target.reactTag == nil && target.superview != nil) {
1107 target = target.superview;
1108 }
1109
1110 callback(@[
1111 RCTNullIfNil(target.reactTag),
1112 @(frame.origin.x),
1113 @(frame.origin.y),
1114 @(frame.size.width),
1115 @(frame.size.height),
1116 ]);
1117 }];
1118}
1119
1120RCT_EXPORT_METHOD(dispatchViewManagerCommand
1121 : (nonnull NSNumber *)reactTag commandID
1122 : (id /*(NSString or NSNumber) */)commandID commandArgs
1123 : (NSArray<id> *)commandArgs)
1124{
1125 RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
1126 RCTComponentData *componentData = _componentDataByName[shadowView.viewName];
1127
1128 // Achtung! Achtung!
1129 // This is a remarkably hacky and ugly workaround.
1130 // We need this only temporary for some testing. We need this hack until Fabric fully implements command-execution
1131 // pipeline. This does not affect non-Fabric apps.
1132#pragma clang diagnostic push
1133#pragma clang diagnostic ignored "-Wundeclared-selector"
1134 if (!componentData) {
1135 __block RCTPlatformView *view; // TODO(macOS ISS#2323203)
1136 RCTUnsafeExecuteOnMainQueueSync(^{
1137 view = self->_viewRegistry[reactTag];
1138 });
1139 if ([view respondsToSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)]) {
1140 NSString *name = [view performSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)];
1141 componentData = _componentDataByName[[NSString stringWithFormat:@"RCT%@", name]];
1142 }
1143 }
1144#pragma clang diagnostic pop
1145
1146 Class managerClass = componentData.managerClass;
1147 RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
1148
1149 id<RCTBridgeMethod> method;
1150 if ([commandID isKindOfClass:[NSNumber class]]) {
1151 method = moduleData.methods[[commandID intValue]];
1152 } else if ([commandID isKindOfClass:[NSString class]]) {
1153 method = moduleData.methodsByName[commandID];
1154 if (method == nil) {
1155 RCTLogError(@"No command found with name \"%@\"", commandID);
1156 }
1157 } else {
1158 RCTLogError(@"dispatchViewManagerCommand must be called with a string or integer command");
1159 return;
1160 }
1161
1162 NSArray *args = [@[ reactTag ] arrayByAddingObjectsFromArray:commandArgs];
1163 [method invokeWithBridge:_bridge module:componentData.manager arguments:args];
1164}
1165
1166- (void)batchDidComplete
1167{
1168 [self _layoutAndMount];
1169}
1170
1171/**
1172 * Sets up animations, computes layout, creates UI mounting blocks for computed layout,
1173 * runs these blocks and all other already existing blocks.
1174 */
1175- (void)_layoutAndMount
1176{
1177 [self _dispatchPropsDidChangeEvents];
1178 [self _dispatchChildrenDidChangeEvents];
1179
1180 [_observerCoordinator uiManagerWillPerformLayout:self];
1181
1182 // Perform layout
1183 for (NSNumber *reactTag in _rootViewTags) {
1184 RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag];
1185 [self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
1186 }
1187
1188 [_observerCoordinator uiManagerDidPerformLayout:self];
1189
1190 [_observerCoordinator uiManagerWillPerformMounting:self];
1191
1192 [self flushUIBlocksWithCompletion:^{
1193 [self->_observerCoordinator uiManagerDidPerformMounting:self];
1194 }];
1195}
1196
1197- (void)flushUIBlocksWithCompletion:(void (^)(void))completion
1198{
1199 RCTAssertUIManagerQueue();
1200
1201 // First copy the previous blocks into a temporary variable, then reset the
1202 // pending blocks to a new array. This guards against mutation while
1203 // processing the pending blocks in another thread.
1204 NSArray<RCTViewManagerUIBlock> *previousPendingUIBlocks = _pendingUIBlocks;
1205 _pendingUIBlocks = [NSMutableArray new];
1206
1207 if (previousPendingUIBlocks.count == 0) {
1208 completion();
1209 return;
1210 }
1211
1212 __weak typeof(self) weakSelf = self;
1213
1214 void (^mountingBlock)(void) = ^{
1215 typeof(self) strongSelf = weakSelf;
1216
1217 @try {
1218 for (RCTViewManagerUIBlock block in previousPendingUIBlocks) {
1219 block(strongSelf, strongSelf->_viewRegistry);
1220 }
1221 } @catch (NSException *exception) {
1222 RCTLogError(@"Exception thrown while executing UI block: %@", exception);
1223 }
1224 };
1225
1226 if ([self.observerCoordinator uiManager:self performMountingWithBlock:mountingBlock]) {
1227 completion();
1228 return;
1229 }
1230
1231 // Execute the previously queued UI blocks
1232 RCTProfileBeginFlowEvent();
1233 RCTExecuteOnMainQueue(^{
1234 RCTProfileEndFlowEvent();
1235 RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[UIManager flushUIBlocks]", (@{
1236 @"count" : [@(previousPendingUIBlocks.count) stringValue],
1237 }));
1238
1239 mountingBlock();
1240
1241 RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
1242
1243 RCTExecuteOnUIManagerQueue(completion);
1244 });
1245}
1246
1247- (void)setNeedsLayout
1248{
1249 // If there is an active batch layout will happen when batch finished, so we will wait for that.
1250 // Otherwise we immediately trigger layout.
1251 if (![_bridge isBatchActive] && ![_bridge isLoading]) {
1252 [self _layoutAndMount];
1253 }
1254}
1255
1256- (void)_shadowView:(RCTShadowView *)shadowView didReceiveUpdatedProps:(NSArray<NSString *> *)props
1257{
1258 // We collect a set with changed `shadowViews` and its changed props,
1259 // so we have to maintain this collection properly.
1260 NSArray<NSString *> *previousProps;
1261 if ((previousProps = [_shadowViewsWithUpdatedProps objectForKey:shadowView])) {
1262 // Merging already registered changed props and new ones.
1263 NSMutableSet *set = [NSMutableSet setWithArray:previousProps];
1264 [set addObjectsFromArray:props];
1265 props = [set allObjects];
1266 }
1267
1268 [_shadowViewsWithUpdatedProps setObject:props forKey:shadowView];
1269}
1270
1271- (void)_shadowViewDidReceiveUpdatedChildren:(RCTShadowView *)shadowView
1272{
1273 [_shadowViewsWithUpdatedChildren addObject:shadowView];
1274}
1275
1276- (void)_dispatchChildrenDidChangeEvents
1277{
1278 if (_shadowViewsWithUpdatedChildren.count == 0) {
1279 return;
1280 }
1281
1282 NSHashTable<RCTShadowView *> *shadowViews = _shadowViewsWithUpdatedChildren;
1283 _shadowViewsWithUpdatedChildren = [NSHashTable weakObjectsHashTable];
1284
1285 NSMutableArray *tags = [NSMutableArray arrayWithCapacity:shadowViews.count];
1286
1287 for (RCTShadowView *shadowView in shadowViews) {
1288 [shadowView didUpdateReactSubviews];
1289 [tags addObject:shadowView.reactTag];
1290 }
1291
1292 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTUIView *> *viewRegistry) { // TODO(macOS ISS#3536887)
1293 for (NSNumber *tag in tags) {
1294 RCTUIView<RCTComponent> *view = viewRegistry[tag]; // TODO(macOS ISS#3536887)
1295 [view didUpdateReactSubviews];
1296 }
1297 }];
1298}
1299
1300- (void)_dispatchPropsDidChangeEvents
1301{
1302 if (_shadowViewsWithUpdatedProps.count == 0) {
1303 return;
1304 }
1305
1306 NSMapTable<RCTShadowView *, NSArray<NSString *> *> *shadowViews = _shadowViewsWithUpdatedProps;
1307 _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable];
1308
1309 NSMapTable<NSNumber *, NSArray<NSString *> *> *tags = [NSMapTable strongToStrongObjectsMapTable];
1310
1311 for (RCTShadowView *shadowView in shadowViews) {
1312 NSArray<NSString *> *props = [shadowViews objectForKey:shadowView];
1313 [shadowView didSetProps:props];
1314 [tags setObject:props forKey:shadowView.reactTag];
1315 }
1316
1317 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTUIView *> *viewRegistry) { // TODO(macOS ISS#3536887)
1318 for (NSNumber *tag in tags) {
1319 RCTUIView<RCTComponent> *view = viewRegistry[tag]; // TODO(macOS ISS#3536887)
1320 [view didSetProps:[tags objectForKey:tag]];
1321 }
1322 }];
1323}
1324
1325RCT_EXPORT_METHOD(measure : (nonnull NSNumber *)reactTag callback : (RCTResponseSenderBlock)callback)
1326{
1327 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1328 RCTPlatformView *view = viewRegistry[reactTag]; // TODO(macOS ISS#2323203)
1329 if (!view) {
1330 // this view was probably collapsed out
1331 RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
1332 callback(@[]);
1333 return;
1334 }
1335
1336 // If in a <Modal>, rootView will be the root of the modal container.
1337 RCTPlatformView *rootView = view; // TODO(macOS ISS#2323203)
1338 while (rootView.superview && ![rootView isReactRootView]) {
1339 rootView = rootView.superview;
1340 }
1341
1342 // By convention, all coordinates, whether they be touch coordinates, or
1343 // measurement coordinates are with respect to the root view.
1344 CGRect frame = view.frame;
1345 CGRect globalBounds = [view convertRect:view.bounds toView:rootView];
1346
1347 callback(@[
1348 @(frame.origin.x),
1349 @(frame.origin.y),
1350 @(globalBounds.size.width),
1351 @(globalBounds.size.height),
1352 @(globalBounds.origin.x),
1353 @(globalBounds.origin.y),
1354 ]);
1355 }];
1356}
1357
1358RCT_EXPORT_METHOD(measureInWindow : (nonnull NSNumber *)reactTag callback : (RCTResponseSenderBlock)callback)
1359{
1360 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1361 RCTPlatformView *view = viewRegistry[reactTag]; // TODO(macOS ISS#2323203)
1362 if (!view) {
1363 // this view was probably collapsed out
1364 RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
1365 callback(@[]);
1366 return;
1367 }
1368
1369 // Return frame coordinates in window
1370 CGRect windowFrame = [view convertRect:view.bounds toView:nil];
1371#if TARGET_OS_OSX // [TODO(macOS ISS#2323203)
1372 //The macOS default coordinate system has its origin at the lower left of the drawing area, so we need to flip the y-axis coordinate.
1373 windowFrame.origin.y = view.window.contentView.frame.size.height - windowFrame.origin.y - windowFrame.size.height;
1374#endif // ]TODO(macOS ISS#2323203)
1375
1376 callback(@[
1377 @(windowFrame.origin.x),
1378 @(windowFrame.origin.y),
1379 @(windowFrame.size.width),
1380 @(windowFrame.size.height),
1381 ]);
1382 }];
1383}
1384
1385/**
1386 * Returns if the shadow view provided has the `ancestor` shadow view as
1387 * an actual ancestor.
1388 */
1389RCT_EXPORT_METHOD(viewIsDescendantOf
1390 : (nonnull NSNumber *)reactTag ancestor
1391 : (nonnull NSNumber *)ancestorReactTag callback
1392 : (RCTResponseSenderBlock)callback)
1393{
1394 RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
1395 RCTShadowView *ancestorShadowView = _shadowViewRegistry[ancestorReactTag];
1396 if (!shadowView) {
1397 return;
1398 }
1399 if (!ancestorShadowView) {
1400 return;
1401 }
1402 BOOL viewIsAncestor = [shadowView viewIsDescendantOf:ancestorShadowView];
1403 callback(@[ @(viewIsAncestor) ]);
1404}
1405
1406static void RCTMeasureLayout(RCTShadowView *view, RCTShadowView *ancestor, RCTResponseSenderBlock callback)
1407{
1408 if (!view) {
1409 return;
1410 }
1411 if (!ancestor) {
1412 return;
1413 }
1414 CGRect result = [view measureLayoutRelativeToAncestor:ancestor];
1415 if (CGRectIsNull(result)) {
1416 RCTLogError(
1417 @"view %@ (tag #%@) is not a descendant of %@ (tag #%@)", view, view.reactTag, ancestor, ancestor.reactTag);
1418 return;
1419 }
1420 CGFloat leftOffset = result.origin.x;
1421 CGFloat topOffset = result.origin.y;
1422 CGFloat width = result.size.width;
1423 CGFloat height = result.size.height;
1424 if (isnan(leftOffset) || isnan(topOffset) || isnan(width) || isnan(height)) {
1425 RCTLogError(@"Attempted to measure layout but offset or dimensions were NaN");
1426 return;
1427 }
1428 callback(@[ @(leftOffset), @(topOffset), @(width), @(height) ]);
1429}
1430
1431/**
1432 * Returns the computed recursive offset layout in a dictionary form. The
1433 * returned values are relative to the `ancestor` shadow view. Returns `nil`, if
1434 * the `ancestor` shadow view is not actually an `ancestor`. Does not touch
1435 * anything on the main UI thread. Invokes supplied callback with (x, y, width,
1436 * height).
1437 */
1438RCT_EXPORT_METHOD(measureLayout
1439 : (nonnull NSNumber *)reactTag relativeTo
1440 : (nonnull NSNumber *)ancestorReactTag errorCallback
1441 : (__unused RCTResponseSenderBlock)errorCallback callback
1442 : (RCTResponseSenderBlock)callback)
1443{
1444 RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
1445 RCTShadowView *ancestorShadowView = _shadowViewRegistry[ancestorReactTag];
1446 RCTMeasureLayout(shadowView, ancestorShadowView, callback);
1447}
1448
1449/**
1450 * Returns the computed recursive offset layout in a dictionary form. The
1451 * returned values are relative to the `ancestor` shadow view. Returns `nil`, if
1452 * the `ancestor` shadow view is not actually an `ancestor`. Does not touch
1453 * anything on the main UI thread. Invokes supplied callback with (x, y, width,
1454 * height).
1455 */
1456RCT_EXPORT_METHOD(measureLayoutRelativeToParent
1457 : (nonnull NSNumber *)reactTag errorCallback
1458 : (__unused RCTResponseSenderBlock)errorCallback callback
1459 : (RCTResponseSenderBlock)callback)
1460{
1461 RCTLogWarn(
1462 @"RCTUIManager.measureLayoutRelativeToParent method is deprecated and it will not be implemented in newer versions of RN (Fabric) - T47686450");
1463 RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
1464 RCTMeasureLayout(shadowView, shadowView.reactSuperview, callback);
1465}
1466
1467/**
1468 * JS sets what *it* considers to be the responder. Later, scroll views can use
1469 * this in order to determine if scrolling is appropriate.
1470 */
1471RCT_EXPORT_METHOD(setJSResponder
1472 : (nonnull NSNumber *)reactTag blockNativeResponder
1473 : (__unused BOOL)blockNativeResponder)
1474{
1475 [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1476 _jsResponder = viewRegistry[reactTag];
1477 if (!_jsResponder) {
1478 RCTLogWarn(@"Invalid view set to be the JS responder - tag %@", reactTag);
1479 }
1480 }];
1481}
1482
1483RCT_EXPORT_METHOD(clearJSResponder)
1484{
1485 [self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1486 _jsResponder = nil;
1487 }];
1488}
1489
1490static NSMutableDictionary<NSString *, id> *moduleConstantsForComponent(
1491 NSMutableDictionary<NSString *, NSDictionary *> *directEvents,
1492 NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents,
1493 RCTComponentData *componentData)
1494{
1495 NSMutableDictionary<NSString *, id> *moduleConstants = [NSMutableDictionary new];
1496
1497 // Register which event-types this view dispatches.
1498 // React needs this for the event plugin.
1499 NSMutableDictionary<NSString *, NSDictionary *> *bubblingEventTypes = [NSMutableDictionary new];
1500 NSMutableDictionary<NSString *, NSDictionary *> *directEventTypes = [NSMutableDictionary new];
1501
1502 // Add manager class
1503 moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);
1504
1505 // Add native props
1506 NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
1507 moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
1508 moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
1509 moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes;
1510 moduleConstants[@"directEventTypes"] = directEventTypes;
1511
1512 // Add direct events
1513 for (NSString *eventName in viewConfig[@"directEvents"]) {
1514 if (!directEvents[eventName]) {
1515 directEvents[eventName] = @{
1516 @"registrationName" : [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
1517 };
1518 }
1519 directEventTypes[eventName] = directEvents[eventName];
1520 if (RCT_DEBUG && bubblingEvents[eventName]) {
1521 RCTLogError(
1522 @"Component '%@' re-registered bubbling event '%@' as a "
1523 "direct event",
1524 componentData.name,
1525 eventName);
1526 }
1527 }
1528
1529 // Add bubbling events
1530 for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
1531 if (!bubblingEvents[eventName]) {
1532 NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
1533 bubblingEvents[eventName] = @{
1534 @"phasedRegistrationNames" : @{
1535 @"bubbled" : bubbleName,
1536 @"captured" : [bubbleName stringByAppendingString:@"Capture"],
1537 }
1538 };
1539 }
1540 bubblingEventTypes[eventName] = bubblingEvents[eventName];
1541 if (RCT_DEBUG && directEvents[eventName]) {
1542 RCTLogError(
1543 @"Component '%@' re-registered direct event '%@' as a "
1544 "bubbling event",
1545 componentData.name,
1546 eventName);
1547 }
1548 }
1549
1550 return moduleConstants;
1551}
1552
1553- (NSDictionary<NSString *, id> *)constantsToExport
1554{
1555 return [self getConstants];
1556}
1557
1558- (NSDictionary<NSString *, id> *)getConstants
1559{
1560 NSMutableDictionary<NSString *, NSDictionary *> *constants = [NSMutableDictionary new];
1561 NSMutableDictionary<NSString *, NSDictionary *> *directEvents = [NSMutableDictionary new];
1562 NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents = [NSMutableDictionary new];
1563
1564 [_componentDataByName
1565 enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
1566 RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name);
1567 NSMutableDictionary<NSString *, id> *moduleConstants =
1568 moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
1569 constants[name] = moduleConstants;
1570 }];
1571
1572 return constants;
1573}
1574
1575RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(lazilyLoadView : (NSString *)name)
1576{
1577 if (_componentDataByName[name]) {
1578 return @{};
1579 }
1580
1581 id<RCTBridgeDelegate> delegate = self.bridge.delegate;
1582 if (![delegate respondsToSelector:@selector(bridge:didNotFindModule:)]) {
1583 return @{};
1584 }
1585
1586 NSString *moduleName = name;
1587 BOOL result = [delegate bridge:self.bridge didNotFindModule:moduleName];
1588 if (!result) {
1589 moduleName = [name stringByAppendingString:@"Manager"];
1590 result = [delegate bridge:self.bridge didNotFindModule:moduleName];
1591 }
1592 if (!result) {
1593 return @{};
1594 }
1595
1596 id module = [self.bridge moduleForName:moduleName lazilyLoadIfNecessary:RCTTurboModuleEnabled()];
1597 if (module == nil) {
1598 // There is all sorts of code in this codebase that drops prefixes.
1599 //
1600 // If we didn't find a module, it's possible because it's stored under a key
1601 // which had RCT Prefixes stripped. Lets check one more time...
1602 module = [self.bridge moduleForName:RCTDropReactPrefixes(moduleName) lazilyLoadIfNecessary:RCTTurboModuleEnabled()];
1603 }
1604
1605 if (!module) {
1606 return @{};
1607 }
1608
1609 RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:[module class] bridge:self.bridge];
1610 _componentDataByName[componentData.name] = componentData;
1611 NSMutableDictionary *directEvents = [NSMutableDictionary new];
1612 NSMutableDictionary *bubblingEvents = [NSMutableDictionary new];
1613 NSMutableDictionary<NSString *, id> *moduleConstants =
1614 moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
1615 return @{
1616 @"viewConfig" : moduleConstants,
1617 };
1618}
1619
1620RCT_EXPORT_METHOD(configureNextLayoutAnimation
1621 : (NSDictionary *)config withCallback
1622 : (RCTResponseSenderBlock)callback errorCallback
1623 : (__unused RCTResponseSenderBlock)errorCallback)
1624{
1625 RCTLayoutAnimationGroup *layoutAnimationGroup = [[RCTLayoutAnimationGroup alloc] initWithConfig:config
1626 callback:callback];
1627
1628 [self addUIBlock:^(RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, RCTPlatformView *> *viewRegistry) { // TODO(macOS ISS#2323203)
1629 [uiManager setNextLayoutAnimationGroup:layoutAnimationGroup];
1630 }];
1631}
1632
1633- (void)rootViewForReactTag:(NSNumber *)reactTag withCompletion:(void (^)(RCTPlatformView *view))completion // TODO(macOS ISS#2323203)
1634{
1635 RCTAssertMainQueue();
1636 RCTAssert(completion != nil, @"Attempted to resolve rootView for tag %@ without a completion block", reactTag);
1637
1638 if (reactTag == nil) {
1639 completion(nil);
1640 return;
1641 }
1642
1643 RCTExecuteOnUIManagerQueue(^{
1644 NSNumber *rootTag = [self shadowViewForReactTag:reactTag].rootView.reactTag;
1645 RCTExecuteOnMainQueue(^{
1646 RCTPlatformView *rootView = nil; // TODO(macOS ISS#2323203)
1647 if (rootTag != nil) {
1648 rootView = [self viewForReactTag:rootTag];
1649 }
1650 completion(rootView);
1651 });
1652 });
1653}
1654
1655
1656static RCTPlatformView *_jsResponder; // TODO(macOS ISS#2323203)
1657
1658+ (RCTPlatformView *)JSResponder // TODO(macOS ISS#2323203)
1659{
1660 return _jsResponder;
1661}
1662
1663@end
1664
1665@implementation RCTBridge (RCTUIManager)
1666
1667- (RCTUIManager *)uiManager
1668{
1669 return [self moduleForClass:[RCTUIManager class]];
1670}
1671
1672@end