1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import "RCTUIManager.h"
- #import <AVFoundation/AVFoundation.h>
- #import "RCTAssert.h"
- #import "RCTBridge+Private.h"
- #import "RCTBridge.h"
- #import "RCTComponent.h"
- #import "RCTComponentData.h"
- #import "RCTConvert.h"
- #import "RCTDefines.h"
- #import "RCTEventDispatcher.h"
- #import "RCTLayoutAnimation.h"
- #import "RCTLayoutAnimationGroup.h"
- #import "RCTLog.h"
- #import "RCTModuleData.h"
- #import "RCTModuleMethod.h"
- #import "RCTProfile.h"
- #import "RCTRootContentView.h"
- #import "RCTRootShadowView.h"
- #import "RCTRootViewInternal.h"
- #import "RCTScrollableProtocol.h"
- #import "RCTShadowView+Internal.h"
- #import "RCTShadowView.h"
- #import "RCTSurfaceRootShadowView.h"
- #import "RCTSurfaceRootView.h"
- #import "RCTUIManagerObserverCoordinator.h"
- #import "RCTUIManagerUtils.h"
- #import "RCTUtils.h"
- #import "RCTView.h"
- #import "RCTViewManager.h"
- #import "UIView+React.h"
- static void RCTTraverseViewNodes(id<RCTComponent> view, void (^block)(id<RCTComponent>))
- {
- if (view.reactTag) {
- block(view);
- for (id<RCTComponent> subview in view.reactSubviews) {
- RCTTraverseViewNodes(subview, block);
- }
- }
- }
- static NSString *RCTNativeIDRegistryKey(NSString *nativeID, NSNumber *rootTag)
- {
- if (!nativeID || !rootTag) {
- return @"";
- }
- return [NSString stringWithFormat:@"%@-%@", rootTag, nativeID];
- }
- NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification =
- @"RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification";
- @implementation RCTUIManager {
- // Root views are only mutated on the shadow queue
- NSMutableSet<NSNumber *> *_rootViewTags;
- NSMutableArray<RCTViewManagerUIBlock> *_pendingUIBlocks;
- // Animation
- RCTLayoutAnimationGroup *_layoutAnimationGroup; // Main thread only
- NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
- NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
- NSMapTable<NSString *, UIView *> *_nativeIDRegistry;
- NSMapTable<RCTShadowView *, NSArray<NSString *> *> *_shadowViewsWithUpdatedProps; // UIManager queue only.
- NSHashTable<RCTShadowView *> *_shadowViewsWithUpdatedChildren; // UIManager queue only.
- // Keyed by viewName
- NSMutableDictionary *_componentDataByName;
- }
- @synthesize bridge = _bridge;
- RCT_EXPORT_MODULE()
- + (BOOL)requiresMainQueueSetup
- {
- return NO;
- }
- - (void)invalidate
- {
- /**
- * Called on the JS Thread since all modules are invalidated on the JS thread
- */
- // This only accessed from the shadow queue
- _pendingUIBlocks = nil;
- RCTExecuteOnMainQueue(^{
- RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"UIManager invalidate", nil);
- for (NSNumber *rootViewTag in self->_rootViewTags) {
- UIView *rootView = self->_viewRegistry[rootViewTag];
- if ([rootView conformsToProtocol:@protocol(RCTInvalidating)]) {
- [(id<RCTInvalidating>)rootView invalidate];
- }
- }
- self->_rootViewTags = nil;
- self->_shadowViewRegistry = nil;
- self->_viewRegistry = nil;
- self->_nativeIDRegistry = nil;
- self->_bridge = nil;
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
- });
- }
- - (NSMutableDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
- {
- // NOTE: this method only exists so that it can be accessed by unit tests
- if (!_shadowViewRegistry) {
- _shadowViewRegistry = [NSMutableDictionary new];
- }
- return _shadowViewRegistry;
- }
- - (NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry
- {
- // NOTE: this method only exists so that it can be accessed by unit tests
- if (!_viewRegistry) {
- _viewRegistry = [NSMutableDictionary new];
- }
- return _viewRegistry;
- }
- - (NSMapTable *)nativeIDRegistry
- {
- if (!_nativeIDRegistry) {
- _nativeIDRegistry = [NSMapTable strongToWeakObjectsMapTable];
- }
- return _nativeIDRegistry;
- }
- - (void)setBridge:(RCTBridge *)bridge
- {
- RCTAssert(_bridge == nil, @"Should not re-use same UIManager instance");
- _bridge = bridge;
- _shadowViewRegistry = [NSMutableDictionary new];
- _viewRegistry = [NSMutableDictionary new];
- _nativeIDRegistry = [NSMapTable strongToWeakObjectsMapTable];
- _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable];
- _shadowViewsWithUpdatedChildren = [NSHashTable weakObjectsHashTable];
- // Internal resources
- _pendingUIBlocks = [NSMutableArray new];
- _rootViewTags = [NSMutableSet new];
- _observerCoordinator = [RCTUIManagerObserverCoordinator new];
- // Get view managers from bridge=
- _componentDataByName = [NSMutableDictionary new];
- for (Class moduleClass in _bridge.moduleClasses) {
- if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
- RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass bridge:_bridge];
- _componentDataByName[componentData.name] = componentData;
- }
- }
- // This dispatch_async avoids a deadlock while configuring native modules
- dispatch_async(dispatch_get_main_queue(), ^{
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(didReceiveNewContentSizeMultiplier)
- name:@"RCTAccessibilityManagerDidUpdateMultiplierNotification"
- object:[self->_bridge moduleForName:@"AccessibilityManager"
- lazilyLoadIfNecessary:YES]];
- });
- #if !TARGET_OS_TV
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(namedOrientationDidChange)
- name:UIDeviceOrientationDidChangeNotification
- object:nil];
- #endif
- [RCTLayoutAnimation initializeStatics];
- }
- #pragma mark - Event emitting
- - (void)didReceiveNewContentSizeMultiplier
- {
- // Report the event across the bridge.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- id multiplier = [[self->_bridge moduleForName:@"AccessibilityManager"
- lazilyLoadIfNecessary:YES] valueForKey:@"multiplier"];
- if (multiplier) {
- [_bridge.eventDispatcher sendDeviceEventWithName:@"didUpdateContentSizeMultiplier" body:multiplier];
- }
- #pragma clang diagnostic pop
- RCTExecuteOnUIManagerQueue(^{
- [[NSNotificationCenter defaultCenter]
- postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
- object:self];
- [self setNeedsLayout];
- });
- }
- #if !TARGET_OS_TV
- // Names and coordinate system from html5 spec:
- // https://developer.mozilla.org/en-US/docs/Web/API/Screen.orientation
- // https://developer.mozilla.org/en-US/docs/Web/API/Screen.lockOrientation
- static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
- {
- NSString *name;
- NSNumber *degrees = @0;
- BOOL isLandscape = NO;
- switch (orientation) {
- case UIDeviceOrientationPortrait:
- name = @"portrait-primary";
- break;
- case UIDeviceOrientationPortraitUpsideDown:
- name = @"portrait-secondary";
- degrees = @180;
- break;
- case UIDeviceOrientationLandscapeRight:
- name = @"landscape-primary";
- degrees = @-90;
- isLandscape = YES;
- break;
- case UIDeviceOrientationLandscapeLeft:
- name = @"landscape-secondary";
- degrees = @90;
- isLandscape = YES;
- break;
- case UIDeviceOrientationFaceDown:
- case UIDeviceOrientationFaceUp:
- case UIDeviceOrientationUnknown:
- // Unsupported
- return nil;
- }
- return @{
- @"name" : name,
- @"rotationDegrees" : degrees,
- @"isLandscape" : @(isLandscape),
- };
- }
- - (void)namedOrientationDidChange
- {
- NSDictionary *orientationEvent = deviceOrientationEventBody([UIDevice currentDevice].orientation);
- if (!orientationEvent) {
- return;
- }
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- [_bridge.eventDispatcher sendDeviceEventWithName:@"namedOrientationDidChange" body:orientationEvent];
- #pragma clang diagnostic pop
- }
- #endif
- - (dispatch_queue_t)methodQueue
- {
- return RCTGetUIManagerQueue();
- }
- - (void)registerRootViewTag:(NSNumber *)rootTag
- {
- RCTAssertUIManagerQueue();
- RCTAssert(RCTIsReactRootView(rootTag), @"Attempt to register rootTag (%@) which is not actually root tag.", rootTag);
- RCTAssert(
- ![_rootViewTags containsObject:rootTag],
- @"Attempt to register rootTag (%@) which was already registered.",
- rootTag);
- [_rootViewTags addObject:rootTag];
- // Registering root shadow view
- RCTSurfaceRootShadowView *shadowView = [RCTSurfaceRootShadowView new];
- shadowView.reactTag = rootTag;
- _shadowViewRegistry[rootTag] = shadowView;
- // Registering root view
- RCTExecuteOnMainQueue(^{
- RCTSurfaceRootView *rootView = [RCTSurfaceRootView new];
- rootView.reactTag = rootTag;
- self->_viewRegistry[rootTag] = rootView;
- });
- }
- - (void)registerRootView:(RCTRootContentView *)rootView
- {
- RCTAssertMainQueue();
- NSNumber *reactTag = rootView.reactTag;
- RCTAssert(RCTIsReactRootView(reactTag), @"View %@ with tag #%@ is not a root view", rootView, reactTag);
- UIView *existingView = _viewRegistry[reactTag];
- RCTAssert(
- existingView == nil || existingView == rootView,
- @"Expect all root views to have unique tag. Added %@ twice",
- reactTag);
- CGSize availableSize = rootView.availableSize;
- // Register view
- _viewRegistry[reactTag] = rootView;
- // Register shadow view
- RCTExecuteOnUIManagerQueue(^{
- if (!self->_viewRegistry) {
- return;
- }
- RCTRootShadowView *shadowView = [RCTRootShadowView new];
- shadowView.availableSize = availableSize;
- shadowView.reactTag = reactTag;
- shadowView.viewName = NSStringFromClass([rootView class]);
- self->_shadowViewRegistry[shadowView.reactTag] = shadowView;
- [self->_rootViewTags addObject:reactTag];
- });
- }
- - (NSString *)viewNameForReactTag:(NSNumber *)reactTag
- {
- RCTAssertUIManagerQueue();
- NSString *name = _shadowViewRegistry[reactTag].viewName;
- if (name) {
- return name;
- }
- __block UIView *view;
- RCTUnsafeExecuteOnMainQueueSync(^{
- view = self->_viewRegistry[reactTag];
- });
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wundeclared-selector"
- if ([view respondsToSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)]) {
- return [view performSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)];
- }
- #pragma clang diagnostic pop
- return nil;
- }
- - (UIView *)viewForReactTag:(NSNumber *)reactTag
- {
- RCTAssertMainQueue();
- return _viewRegistry[reactTag];
- }
- - (RCTShadowView *)shadowViewForReactTag:(NSNumber *)reactTag
- {
- RCTAssertUIManagerQueue();
- return _shadowViewRegistry[reactTag];
- }
- - (void)_executeBlockWithShadowView:(void (^)(RCTShadowView *shadowView))block forTag:(NSNumber *)tag
- {
- RCTAssertMainQueue();
- RCTExecuteOnUIManagerQueue(^{
- RCTShadowView *shadowView = self->_shadowViewRegistry[tag];
- if (shadowView == nil) {
- RCTLogInfo(
- @"Could not locate shadow view with tag #%@, this is probably caused by a temporary inconsistency between native views and shadow views.",
- tag);
- return;
- }
- block(shadowView);
- });
- }
- - (void)setAvailableSize:(CGSize)availableSize forRootView:(UIView *)rootView
- {
- RCTAssertMainQueue();
- [self
- _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
- RCTAssert(
- [shadowView isKindOfClass:[RCTRootShadowView class]], @"Located shadow view is actually not root view.");
- RCTRootShadowView *rootShadowView = (RCTRootShadowView *)shadowView;
- if (CGSizeEqualToSize(availableSize, rootShadowView.availableSize)) {
- return;
- }
- rootShadowView.availableSize = availableSize;
- [self setNeedsLayout];
- }
- forTag:rootView.reactTag];
- }
- - (void)setLocalData:(NSObject *)localData forView:(UIView *)view
- {
- RCTAssertMainQueue();
- [self
- _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
- shadowView.localData = localData;
- [self setNeedsLayout];
- }
- forTag:view.reactTag];
- }
- - (UIView *)viewForNativeID:(NSString *)nativeID withRootTag:(NSNumber *)rootTag
- {
- if (!nativeID || !rootTag) {
- return nil;
- }
- UIView *view;
- @synchronized(self) {
- view = [_nativeIDRegistry objectForKey:RCTNativeIDRegistryKey(nativeID, rootTag)];
- }
- return view;
- }
- - (void)setNativeID:(NSString *)nativeID forView:(UIView *)view
- {
- if (!nativeID || !view) {
- return;
- }
- __weak RCTUIManager *weakSelf = self;
- RCTExecuteOnUIManagerQueue(^{
- NSNumber *rootTag = [weakSelf shadowViewForReactTag:view.reactTag].rootView.reactTag;
- @synchronized(weakSelf) {
- [weakSelf.nativeIDRegistry setObject:view forKey:RCTNativeIDRegistryKey(nativeID, rootTag)];
- }
- });
- }
- - (void)setSize:(CGSize)size forView:(UIView *)view
- {
- RCTAssertMainQueue();
- [self
- _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
- if (CGSizeEqualToSize(size, shadowView.size)) {
- return;
- }
- shadowView.size = size;
- [self setNeedsLayout];
- }
- forTag:view.reactTag];
- }
- - (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize forView:(UIView *)view
- {
- RCTAssertMainQueue();
- [self
- _executeBlockWithShadowView:^(RCTShadowView *shadowView) {
- if (CGSizeEqualToSize(shadowView.intrinsicContentSize, intrinsicContentSize)) {
- return;
- }
- shadowView.intrinsicContentSize = intrinsicContentSize;
- [self setNeedsLayout];
- }
- forTag:view.reactTag];
- }
- /**
- * Unregisters views from registries
- */
- - (void)_purgeChildren:(NSArray<id<RCTComponent>> *)children
- fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
- {
- for (id<RCTComponent> child in children) {
- RCTTraverseViewNodes(registry[child.reactTag], ^(id<RCTComponent> subview) {
- RCTAssert(![subview isReactRootView], @"Root views should not be unregistered");
- if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) {
- [(id<RCTInvalidating>)subview invalidate];
- }
- [registry removeObjectForKey:subview.reactTag];
- });
- }
- }
- - (void)addUIBlock:(RCTViewManagerUIBlock)block
- {
- RCTAssertUIManagerQueue();
- if (!block || !_viewRegistry) {
- return;
- }
- [_pendingUIBlocks addObject:block];
- }
- - (void)prependUIBlock:(RCTViewManagerUIBlock)block
- {
- RCTAssertUIManagerQueue();
- if (!block || !_viewRegistry) {
- return;
- }
- [_pendingUIBlocks insertObject:block atIndex:0];
- }
- - (void)setNextLayoutAnimationGroup:(RCTLayoutAnimationGroup *)layoutAnimationGroup
- {
- RCTAssertMainQueue();
- if (_layoutAnimationGroup && ![_layoutAnimationGroup isEqual:layoutAnimationGroup]) {
- RCTLogWarn(
- @"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.",
- [_layoutAnimationGroup description],
- [layoutAnimationGroup description]);
- }
- _layoutAnimationGroup = layoutAnimationGroup;
- }
- - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView
- {
- RCTAssertUIManagerQueue();
- 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(^{
- UIView *view = self->_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->_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;
- }
- 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);
- }
- }
- // Clean up
- uiManager->_layoutAnimationGroup = nil;
- };
- }
- /**
- * A method to be called from JS, which takes a container ID and then releases
- * all subviews for that container upon receipt.
- */
- RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID : (nonnull NSNumber *)containerID)
- {
- RCTLogWarn(
- @"RCTUIManager.removeSubviewsFromContainerWithID method is deprecated and it will not be implemented in newer versions of RN (Fabric) - T47686450");
- id<RCTComponent> container = _shadowViewRegistry[containerID];
- RCTAssert(container != nil, @"container view (for ID %@) not found", containerID);
- NSUInteger subviewsCount = [container reactSubviews].count;
- NSMutableArray<NSNumber *> *indices = [[NSMutableArray alloc] initWithCapacity:subviewsCount];
- for (NSUInteger childIndex = 0; childIndex < subviewsCount; childIndex++) {
- [indices addObject:@(childIndex)];
- }
- [self manageChildren:containerID
- moveFromIndices:nil
- moveToIndices:nil
- addChildReactTags:nil
- addAtIndices:nil
- removeAtIndices:indices];
- }
- /**
- * Disassociates children from container. Doesn't remove from registries.
- * TODO: use [NSArray getObjects:buffer] to reuse same fast buffer each time.
- *
- * @returns Array of removed items.
- */
- - (NSArray<id<RCTComponent>> *)_childrenToRemoveFromContainer:(id<RCTComponent>)container
- atIndices:(NSArray<NSNumber *> *)atIndices
- {
- // If there are no indices to move or the container has no subviews don't bother
- // We support parents with nil subviews so long as they're all nil so this allows for this behavior
- if (atIndices.count == 0 || [container reactSubviews].count == 0) {
- return nil;
- }
- // Construction of removed children must be done "up front", before indices are disturbed by removals.
- NSMutableArray<id<RCTComponent>> *removedChildren = [NSMutableArray arrayWithCapacity:atIndices.count];
- RCTAssert(container != nil, @"container view (for ID %@) not found", container);
- for (NSNumber *indexNumber in atIndices) {
- NSUInteger index = indexNumber.unsignedIntegerValue;
- if (index < [container reactSubviews].count) {
- [removedChildren addObject:[container reactSubviews][index]];
- }
- }
- if (removedChildren.count != atIndices.count) {
- NSString *message = [NSString stringWithFormat:@"removedChildren count (%tu) was not what we expected (%tu)",
- removedChildren.count,
- atIndices.count];
- RCTFatal(RCTErrorWithMessage(message));
- }
- return removedChildren;
- }
- - (void)_removeChildren:(NSArray<id<RCTComponent>> *)children fromContainer:(id<RCTComponent>)container
- {
- for (id<RCTComponent> removedChild in children) {
- [container removeReactSubview:removedChild];
- }
- }
- /**
- * Remove subviews from their parent with an animation.
- */
- - (void)_removeChildren:(NSArray<UIView *> *)children
- fromContainer:(UIView *)container
- withAnimation:(RCTLayoutAnimationGroup *)animation
- {
- RCTAssertMainQueue();
- RCTLayoutAnimation *deletingLayoutAnimation = animation.deletingLayoutAnimation;
- __block NSUInteger completionsCalled = 0;
- for (UIView *removedChild in children) {
- void (^completion)(BOOL) = ^(BOOL finished) {
- completionsCalled++;
- [removedChild removeFromSuperview];
- if (animation.callback && completionsCalled == children.count) {
- animation.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.
- animation.callback = nil;
- }
- };
- // Hack: At this moment we have two contradict intents.
- // First one: We want to delete the view from view hierarchy.
- // Second one: We want to animate this view, which implies the existence of this view in the hierarchy.
- // So, we have to remove this view from React's view hierarchy but postpone removing from UIKit's hierarchy.
- // Here the problem: the default implementation of `-[UIView removeReactSubview:]` also removes the view from
- // UIKit's hierarchy. So, let's temporary restore the view back after removing. To do so, we have to memorize
- // original `superview` (which can differ from `container`) and an index of removed view.
- UIView *originalSuperview = removedChild.superview;
- NSUInteger originalIndex = [originalSuperview.subviews indexOfObjectIdenticalTo:removedChild];
- [container removeReactSubview:removedChild];
- // Disable user interaction while the view is animating
- // since the view is (conceptually) deleted and not supposed to be interactive.
- removedChild.userInteractionEnabled = NO;
- [originalSuperview insertSubview:removedChild atIndex:originalIndex];
- NSString *property = deletingLayoutAnimation.property;
- [deletingLayoutAnimation
- performAnimations:^{
- if ([property isEqualToString:@"scaleXY"]) {
- removedChild.layer.transform = CATransform3DMakeScale(0.001, 0.001, 0.001);
- } else if ([property isEqualToString:@"scaleX"]) {
- removedChild.layer.transform = CATransform3DMakeScale(0.001, 1, 0.001);
- } else if ([property isEqualToString:@"scaleY"]) {
- removedChild.layer.transform = CATransform3DMakeScale(1, 0.001, 0.001);
- } else if ([property isEqualToString:@"opacity"]) {
- removedChild.layer.opacity = 0.0;
- } else {
- RCTLogError(@"Unsupported layout animation createConfig property %@", deletingLayoutAnimation.property);
- }
- }
- withCompletionBlock:completion];
- }
- }
- RCT_EXPORT_METHOD(removeRootView : (nonnull NSNumber *)rootReactTag)
- {
- RCTShadowView *rootShadowView = _shadowViewRegistry[rootReactTag];
- RCTAssert(rootShadowView.superview == nil, @"root view cannot have superview (ID %@)", rootReactTag);
- [self _purgeChildren:(NSArray<id<RCTComponent>> *)rootShadowView.reactSubviews
- fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];
- [_shadowViewRegistry removeObjectForKey:rootReactTag];
- [_rootViewTags removeObject:rootReactTag];
- [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- RCTAssertMainQueue();
- UIView *rootView = viewRegistry[rootReactTag];
- [uiManager _purgeChildren:(NSArray<id<RCTComponent>> *)rootView.reactSubviews
- fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry];
- [(NSMutableDictionary *)viewRegistry removeObjectForKey:rootReactTag];
- }];
- }
- RCT_EXPORT_METHOD(replaceExistingNonRootView : (nonnull NSNumber *)reactTag withView : (nonnull NSNumber *)newReactTag)
- {
- RCTLogWarn(
- @"RCTUIManager.replaceExistingNonRootView method is deprecated and it will not be implemented in newer versions of RN (Fabric) - T47686450");
- RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
- RCTAssert(shadowView != nil, @"shadowView (for ID %@) not found", reactTag);
- RCTShadowView *superShadowView = shadowView.superview;
- if (!superShadowView) {
- RCTAssert(NO, @"shadowView super (of ID %@) not found", reactTag);
- return;
- }
- NSUInteger indexOfView = [superShadowView.reactSubviews indexOfObjectIdenticalTo:shadowView];
- RCTAssert(indexOfView != NSNotFound, @"View's superview doesn't claim it as subview (id %@)", reactTag);
- NSArray<NSNumber *> *removeAtIndices = @[ @(indexOfView) ];
- NSArray<NSNumber *> *addTags = @[ newReactTag ];
- [self manageChildren:superShadowView.reactTag
- moveFromIndices:nil
- moveToIndices:nil
- addChildReactTags:addTags
- addAtIndices:removeAtIndices
- removeAtIndices:removeAtIndices];
- }
- RCT_EXPORT_METHOD(setChildren : (nonnull NSNumber *)containerTag reactTags : (NSArray<NSNumber *> *)reactTags)
- {
- RCTSetChildren(containerTag, reactTags, (NSDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry);
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- RCTSetChildren(containerTag, reactTags, (NSDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry);
- }];
- [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]];
- }
- static void RCTSetChildren(
- NSNumber *containerTag,
- NSArray<NSNumber *> *reactTags,
- NSDictionary<NSNumber *, id<RCTComponent>> *registry)
- {
- id<RCTComponent> container = registry[containerTag];
- NSInteger index = 0;
- for (NSNumber *reactTag in reactTags) {
- id<RCTComponent> view = registry[reactTag];
- if (view) {
- [container insertReactSubview:view atIndex:index++];
- }
- }
- }
- RCT_EXPORT_METHOD(manageChildren
- : (nonnull NSNumber *)containerTag moveFromIndices
- : (NSArray<NSNumber *> *)moveFromIndices moveToIndices
- : (NSArray<NSNumber *> *)moveToIndices addChildReactTags
- : (NSArray<NSNumber *> *)addChildReactTags addAtIndices
- : (NSArray<NSNumber *> *)addAtIndices removeAtIndices
- : (NSArray<NSNumber *> *)removeAtIndices)
- {
- [self _manageChildren:containerTag
- moveFromIndices:moveFromIndices
- moveToIndices:moveToIndices
- addChildReactTags:addChildReactTags
- addAtIndices:addAtIndices
- removeAtIndices:removeAtIndices
- registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];
- [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- [uiManager _manageChildren:containerTag
- moveFromIndices:moveFromIndices
- moveToIndices:moveToIndices
- addChildReactTags:addChildReactTags
- addAtIndices:addAtIndices
- removeAtIndices:removeAtIndices
- registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry];
- }];
- [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]];
- }
- - (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
- {
- id<RCTComponent> container = registry[containerTag];
- RCTAssert(
- moveFromIndices.count == moveToIndices.count,
- @"moveFromIndices had size %tu, moveToIndices had size %tu",
- moveFromIndices.count,
- moveToIndices.count);
- RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add");
- // Removes (both permanent and temporary moves) are using "before" indices
- NSArray<id<RCTComponent>> *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container
- atIndices:removeAtIndices];
- NSArray<id<RCTComponent>> *temporarilyRemovedChildren = [self _childrenToRemoveFromContainer:container
- atIndices:moveFromIndices];
- BOOL isUIViewRegistry = ((id)registry == (id)_viewRegistry);
- if (isUIViewRegistry && _layoutAnimationGroup.deletingLayoutAnimation) {
- [self _removeChildren:(NSArray<UIView *> *)permanentlyRemovedChildren
- fromContainer:(UIView *)container
- withAnimation:_layoutAnimationGroup];
- } else {
- [self _removeChildren:permanentlyRemovedChildren fromContainer:container];
- }
- [self _removeChildren:temporarilyRemovedChildren fromContainer:container];
- [self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
- // Figure out what to insert - merge temporary inserts and adds
- NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
- for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
- destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
- }
- for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
- id<RCTComponent> view = registry[addChildReactTags[index]];
- if (view) {
- destinationsToChildrenToAdd[addAtIndices[index]] = view;
- }
- }
- NSArray<NSNumber *> *sortedIndices =
- [destinationsToChildrenToAdd.allKeys sortedArrayUsingSelector:@selector(compare:)];
- for (NSNumber *reactIndex in sortedIndices) {
- [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue];
- }
- }
- RCT_EXPORT_METHOD(createView
- : (nonnull NSNumber *)reactTag viewName
- : (NSString *)viewName rootTag
- : (nonnull NSNumber *)rootTag props
- : (NSDictionary *)props)
- {
- RCTComponentData *componentData = _componentDataByName[viewName];
- if (componentData == nil) {
- RCTLogError(@"No component found for view with name \"%@\"", viewName);
- }
- // Register shadow view
- RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];
- if (shadowView) {
- [componentData setProps:props forShadowView:shadowView];
- _shadowViewRegistry[reactTag] = shadowView;
- RCTShadowView *rootView = _shadowViewRegistry[rootTag];
- RCTAssert(
- [rootView isKindOfClass:[RCTRootShadowView class]] || [rootView isKindOfClass:[RCTSurfaceRootShadowView class]],
- @"Given `rootTag` (%@) does not correspond to a valid root shadow view instance.",
- rootTag);
- shadowView.rootView = (RCTRootShadowView *)rootView;
- }
- // Dispatch view creation directly to the main thread instead of adding to
- // UIBlocks array. This way, it doesn't get deferred until after layout.
- __block UIView *preliminaryCreatedView = nil;
- void (^createViewBlock)(void) = ^{
- // Do nothing on the second run.
- if (preliminaryCreatedView) {
- return;
- }
- preliminaryCreatedView = [componentData createViewWithTag:reactTag rootTag:rootTag];
- if (preliminaryCreatedView) {
- self->_viewRegistry[reactTag] = preliminaryCreatedView;
- }
- };
- // We cannot guarantee that asynchronously scheduled block will be executed
- // *before* a block is added to the regular mounting process (simply because
- // mounting process can be managed externally while the main queue is
- // locked).
- // So, we positively dispatch it asynchronously and double check inside
- // the regular mounting block.
- RCTExecuteOnMainQueue(createViewBlock);
- [self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- createViewBlock();
- if (preliminaryCreatedView) {
- [componentData setProps:props forView:preliminaryCreatedView];
- }
- }];
- [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]];
- }
- RCT_EXPORT_METHOD(updateView
- : (nonnull NSNumber *)reactTag viewName
- : (NSString *)viewName // not always reliable, use shadowView.viewName if available
- props
- : (NSDictionary *)props)
- {
- RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
- RCTComponentData *componentData = _componentDataByName[shadowView.viewName ?: viewName];
- [componentData setProps:props forShadowView:shadowView];
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- UIView *view = viewRegistry[reactTag];
- [componentData setProps:props forView:view];
- }];
- [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]];
- }
- - (void)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag viewName:(NSString *)viewName props:(NSDictionary *)props
- {
- RCTAssertMainQueue();
- RCTComponentData *componentData = _componentDataByName[viewName];
- UIView *view = _viewRegistry[reactTag];
- [componentData setProps:props forView:view];
- }
- RCT_EXPORT_METHOD(focus : (nonnull NSNumber *)reactTag)
- {
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- UIView *newResponder = viewRegistry[reactTag];
- [newResponder reactFocus];
- }];
- }
- RCT_EXPORT_METHOD(blur : (nonnull NSNumber *)reactTag)
- {
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- UIView *currentResponder = viewRegistry[reactTag];
- [currentResponder reactBlur];
- }];
- }
- RCT_EXPORT_METHOD(findSubviewIn
- : (nonnull NSNumber *)reactTag atPoint
- : (CGPoint)point callback
- : (RCTResponseSenderBlock)callback)
- {
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- UIView *view = viewRegistry[reactTag];
- UIView *target = [view hitTest:point withEvent:nil];
- CGRect frame = [target convertRect:target.bounds toView:view];
- while (target.reactTag == nil && target.superview != nil) {
- target = target.superview;
- }
- callback(@[
- RCTNullIfNil(target.reactTag),
- @(frame.origin.x),
- @(frame.origin.y),
- @(frame.size.width),
- @(frame.size.height),
- ]);
- }];
- }
- RCT_EXPORT_METHOD(dispatchViewManagerCommand
- : (nonnull NSNumber *)reactTag commandID
- : (id /*(NSString or NSNumber) */)commandID commandArgs
- : (NSArray<id> *)commandArgs)
- {
- RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
- RCTComponentData *componentData = _componentDataByName[shadowView.viewName];
- // Achtung! Achtung!
- // This is a remarkably hacky and ugly workaround.
- // We need this only temporary for some testing. We need this hack until Fabric fully implements command-execution
- // pipeline. This does not affect non-Fabric apps.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wundeclared-selector"
- if (!componentData) {
- __block UIView *view;
- RCTUnsafeExecuteOnMainQueueSync(^{
- view = self->_viewRegistry[reactTag];
- });
- if ([view respondsToSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)]) {
- NSString *name = [view performSelector:@selector(componentViewName_DO_NOT_USE_THIS_IS_BROKEN)];
- componentData = _componentDataByName[[NSString stringWithFormat:@"RCT%@", name]];
- }
- }
- #pragma clang diagnostic pop
- Class managerClass = componentData.managerClass;
- RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
- id<RCTBridgeMethod> method;
- if ([commandID isKindOfClass:[NSNumber class]]) {
- method = moduleData.methods[[commandID intValue]];
- } else if ([commandID isKindOfClass:[NSString class]]) {
- method = moduleData.methodsByName[commandID];
- if (method == nil) {
- RCTLogError(@"No command found with name \"%@\"", commandID);
- }
- } else {
- RCTLogError(@"dispatchViewManagerCommand must be called with a string or integer command");
- return;
- }
- NSArray *args = [@[ reactTag ] arrayByAddingObjectsFromArray:commandArgs];
- [method invokeWithBridge:_bridge module:componentData.manager arguments:args];
- }
- - (void)batchDidComplete
- {
- [self _layoutAndMount];
- }
- /**
- * Sets up animations, computes layout, creates UI mounting blocks for computed layout,
- * runs these blocks and all other already existing blocks.
- */
- - (void)_layoutAndMount
- {
- [self _dispatchPropsDidChangeEvents];
- [self _dispatchChildrenDidChangeEvents];
- [_observerCoordinator uiManagerWillPerformLayout:self];
- // Perform layout
- for (NSNumber *reactTag in _rootViewTags) {
- RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag];
- [self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
- }
- [_observerCoordinator uiManagerDidPerformLayout:self];
- [_observerCoordinator uiManagerWillPerformMounting:self];
- [self flushUIBlocksWithCompletion:^{
- [self->_observerCoordinator uiManagerDidPerformMounting:self];
- }];
- }
- - (void)flushUIBlocksWithCompletion:(void (^)(void))completion
- {
- RCTAssertUIManagerQueue();
- // First copy the previous blocks into a temporary variable, then reset the
- // pending blocks to a new array. This guards against mutation while
- // processing the pending blocks in another thread.
- NSArray<RCTViewManagerUIBlock> *previousPendingUIBlocks = _pendingUIBlocks;
- _pendingUIBlocks = [NSMutableArray new];
- if (previousPendingUIBlocks.count == 0) {
- completion();
- return;
- }
- __weak typeof(self) weakSelf = self;
- void (^mountingBlock)(void) = ^{
- typeof(self) strongSelf = weakSelf;
- @try {
- for (RCTViewManagerUIBlock block in previousPendingUIBlocks) {
- block(strongSelf, strongSelf->_viewRegistry);
- }
- } @catch (NSException *exception) {
- RCTLogError(@"Exception thrown while executing UI block: %@", exception);
- }
- };
- if ([self.observerCoordinator uiManager:self performMountingWithBlock:mountingBlock]) {
- completion();
- return;
- }
- // Execute the previously queued UI blocks
- RCTProfileBeginFlowEvent();
- RCTExecuteOnMainQueue(^{
- RCTProfileEndFlowEvent();
- RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[UIManager flushUIBlocks]", (@{
- @"count" : [@(previousPendingUIBlocks.count) stringValue],
- }));
- mountingBlock();
- RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
- RCTExecuteOnUIManagerQueue(completion);
- });
- }
- - (void)setNeedsLayout
- {
- // If there is an active batch layout will happen when batch finished, so we will wait for that.
- // Otherwise we immediately trigger layout.
- if (![_bridge isBatchActive] && ![_bridge isLoading]) {
- [self _layoutAndMount];
- }
- }
- - (void)_shadowView:(RCTShadowView *)shadowView didReceiveUpdatedProps:(NSArray<NSString *> *)props
- {
- // We collect a set with changed `shadowViews` and its changed props,
- // so we have to maintain this collection properly.
- NSArray<NSString *> *previousProps;
- if ((previousProps = [_shadowViewsWithUpdatedProps objectForKey:shadowView])) {
- // Merging already registered changed props and new ones.
- NSMutableSet *set = [NSMutableSet setWithArray:previousProps];
- [set addObjectsFromArray:props];
- props = [set allObjects];
- }
- [_shadowViewsWithUpdatedProps setObject:props forKey:shadowView];
- }
- - (void)_shadowViewDidReceiveUpdatedChildren:(RCTShadowView *)shadowView
- {
- [_shadowViewsWithUpdatedChildren addObject:shadowView];
- }
- - (void)_dispatchChildrenDidChangeEvents
- {
- if (_shadowViewsWithUpdatedChildren.count == 0) {
- return;
- }
- NSHashTable<RCTShadowView *> *shadowViews = _shadowViewsWithUpdatedChildren;
- _shadowViewsWithUpdatedChildren = [NSHashTable weakObjectsHashTable];
- NSMutableArray *tags = [NSMutableArray arrayWithCapacity:shadowViews.count];
- for (RCTShadowView *shadowView in shadowViews) {
- [shadowView didUpdateReactSubviews];
- [tags addObject:shadowView.reactTag];
- }
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- for (NSNumber *tag in tags) {
- UIView<RCTComponent> *view = viewRegistry[tag];
- [view didUpdateReactSubviews];
- }
- }];
- }
- - (void)_dispatchPropsDidChangeEvents
- {
- if (_shadowViewsWithUpdatedProps.count == 0) {
- return;
- }
- NSMapTable<RCTShadowView *, NSArray<NSString *> *> *shadowViews = _shadowViewsWithUpdatedProps;
- _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable];
- NSMapTable<NSNumber *, NSArray<NSString *> *> *tags = [NSMapTable strongToStrongObjectsMapTable];
- for (RCTShadowView *shadowView in shadowViews) {
- NSArray<NSString *> *props = [shadowViews objectForKey:shadowView];
- [shadowView didSetProps:props];
- [tags setObject:props forKey:shadowView.reactTag];
- }
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- for (NSNumber *tag in tags) {
- UIView<RCTComponent> *view = viewRegistry[tag];
- [view didSetProps:[tags objectForKey:tag]];
- }
- }];
- }
- RCT_EXPORT_METHOD(measure : (nonnull NSNumber *)reactTag callback : (RCTResponseSenderBlock)callback)
- {
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- UIView *view = viewRegistry[reactTag];
- if (!view) {
- // this view was probably collapsed out
- RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
- callback(@[]);
- return;
- }
- // If in a <Modal>, rootView will be the root of the modal container.
- UIView *rootView = view;
- while (rootView.superview && ![rootView isReactRootView]) {
- rootView = rootView.superview;
- }
- // By convention, all coordinates, whether they be touch coordinates, or
- // measurement coordinates are with respect to the root view.
- CGRect frame = view.frame;
- CGRect globalBounds = [view convertRect:view.bounds toView:rootView];
- callback(@[
- @(frame.origin.x),
- @(frame.origin.y),
- @(globalBounds.size.width),
- @(globalBounds.size.height),
- @(globalBounds.origin.x),
- @(globalBounds.origin.y),
- ]);
- }];
- }
- RCT_EXPORT_METHOD(measureInWindow : (nonnull NSNumber *)reactTag callback : (RCTResponseSenderBlock)callback)
- {
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- UIView *view = viewRegistry[reactTag];
- if (!view) {
- // this view was probably collapsed out
- RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
- callback(@[]);
- return;
- }
- // Return frame coordinates in window
- CGRect windowFrame = [view.window convertRect:view.frame fromView:view.superview];
- callback(@[
- @(windowFrame.origin.x),
- @(windowFrame.origin.y),
- @(windowFrame.size.width),
- @(windowFrame.size.height),
- ]);
- }];
- }
- /**
- * Returns if the shadow view provided has the `ancestor` shadow view as
- * an actual ancestor.
- */
- RCT_EXPORT_METHOD(viewIsDescendantOf
- : (nonnull NSNumber *)reactTag ancestor
- : (nonnull NSNumber *)ancestorReactTag callback
- : (RCTResponseSenderBlock)callback)
- {
- RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
- RCTShadowView *ancestorShadowView = _shadowViewRegistry[ancestorReactTag];
- if (!shadowView) {
- return;
- }
- if (!ancestorShadowView) {
- return;
- }
- BOOL viewIsAncestor = [shadowView viewIsDescendantOf:ancestorShadowView];
- callback(@[ @(viewIsAncestor) ]);
- }
- static void RCTMeasureLayout(RCTShadowView *view, RCTShadowView *ancestor, RCTResponseSenderBlock callback)
- {
- if (!view) {
- return;
- }
- if (!ancestor) {
- return;
- }
- CGRect result = [view measureLayoutRelativeToAncestor:ancestor];
- if (CGRectIsNull(result)) {
- RCTLogError(
- @"view %@ (tag #%@) is not a descendant of %@ (tag #%@)", view, view.reactTag, ancestor, ancestor.reactTag);
- return;
- }
- CGFloat leftOffset = result.origin.x;
- CGFloat topOffset = result.origin.y;
- CGFloat width = result.size.width;
- CGFloat height = result.size.height;
- if (isnan(leftOffset) || isnan(topOffset) || isnan(width) || isnan(height)) {
- RCTLogError(@"Attempted to measure layout but offset or dimensions were NaN");
- return;
- }
- callback(@[ @(leftOffset), @(topOffset), @(width), @(height) ]);
- }
- /**
- * Returns the computed recursive offset layout in a dictionary form. The
- * returned values are relative to the `ancestor` shadow view. Returns `nil`, if
- * the `ancestor` shadow view is not actually an `ancestor`. Does not touch
- * anything on the main UI thread. Invokes supplied callback with (x, y, width,
- * height).
- */
- RCT_EXPORT_METHOD(measureLayout
- : (nonnull NSNumber *)reactTag relativeTo
- : (nonnull NSNumber *)ancestorReactTag errorCallback
- : (__unused RCTResponseSenderBlock)errorCallback callback
- : (RCTResponseSenderBlock)callback)
- {
- RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
- RCTShadowView *ancestorShadowView = _shadowViewRegistry[ancestorReactTag];
- RCTMeasureLayout(shadowView, ancestorShadowView, callback);
- }
- /**
- * Returns the computed recursive offset layout in a dictionary form. The
- * returned values are relative to the `ancestor` shadow view. Returns `nil`, if
- * the `ancestor` shadow view is not actually an `ancestor`. Does not touch
- * anything on the main UI thread. Invokes supplied callback with (x, y, width,
- * height).
- */
- RCT_EXPORT_METHOD(measureLayoutRelativeToParent
- : (nonnull NSNumber *)reactTag errorCallback
- : (__unused RCTResponseSenderBlock)errorCallback callback
- : (RCTResponseSenderBlock)callback)
- {
- RCTLogWarn(
- @"RCTUIManager.measureLayoutRelativeToParent method is deprecated and it will not be implemented in newer versions of RN (Fabric) - T47686450");
- RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
- RCTMeasureLayout(shadowView, shadowView.reactSuperview, callback);
- }
- /**
- * JS sets what *it* considers to be the responder. Later, scroll views can use
- * this in order to determine if scrolling is appropriate.
- */
- RCT_EXPORT_METHOD(setJSResponder
- : (nonnull NSNumber *)reactTag blockNativeResponder
- : (__unused BOOL)blockNativeResponder)
- {
- [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- _jsResponder = viewRegistry[reactTag];
- if (!_jsResponder) {
- RCTLogWarn(@"Invalid view set to be the JS responder - tag %@", reactTag);
- }
- }];
- }
- RCT_EXPORT_METHOD(clearJSResponder)
- {
- [self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- _jsResponder = nil;
- }];
- }
- static NSMutableDictionary<NSString *, id> *moduleConstantsForComponent(
- NSMutableDictionary<NSString *, NSDictionary *> *directEvents,
- NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents,
- RCTComponentData *componentData)
- {
- NSMutableDictionary<NSString *, id> *moduleConstants = [NSMutableDictionary new];
- // Register which event-types this view dispatches.
- // React needs this for the event plugin.
- NSMutableDictionary<NSString *, NSDictionary *> *bubblingEventTypes = [NSMutableDictionary new];
- NSMutableDictionary<NSString *, NSDictionary *> *directEventTypes = [NSMutableDictionary new];
- // Add manager class
- moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);
- // Add native props
- NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
- moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
- moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
- moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes;
- moduleConstants[@"directEventTypes"] = directEventTypes;
- // Add direct events
- for (NSString *eventName in viewConfig[@"directEvents"]) {
- if (!directEvents[eventName]) {
- directEvents[eventName] = @{
- @"registrationName" : [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
- };
- }
- directEventTypes[eventName] = directEvents[eventName];
- if (RCT_DEBUG && bubblingEvents[eventName]) {
- RCTLogError(
- @"Component '%@' re-registered bubbling event '%@' as a "
- "direct event",
- componentData.name,
- eventName);
- }
- }
- // Add bubbling events
- for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
- if (!bubblingEvents[eventName]) {
- NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
- bubblingEvents[eventName] = @{
- @"phasedRegistrationNames" : @{
- @"bubbled" : bubbleName,
- @"captured" : [bubbleName stringByAppendingString:@"Capture"],
- }
- };
- }
- bubblingEventTypes[eventName] = bubblingEvents[eventName];
- if (RCT_DEBUG && directEvents[eventName]) {
- RCTLogError(
- @"Component '%@' re-registered direct event '%@' as a "
- "bubbling event",
- componentData.name,
- eventName);
- }
- }
- return moduleConstants;
- }
- - (NSDictionary<NSString *, id> *)constantsToExport
- {
- return [self getConstants];
- }
- - (NSDictionary<NSString *, id> *)getConstants
- {
- NSMutableDictionary<NSString *, NSDictionary *> *constants = [NSMutableDictionary new];
- NSMutableDictionary<NSString *, NSDictionary *> *directEvents = [NSMutableDictionary new];
- NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents = [NSMutableDictionary new];
- [_componentDataByName
- enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
- RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name);
- NSMutableDictionary<NSString *, id> *moduleConstants =
- moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
- constants[name] = moduleConstants;
- }];
- return constants;
- }
- RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(lazilyLoadView : (NSString *)name)
- {
- if (_componentDataByName[name]) {
- return @{};
- }
- id<RCTBridgeDelegate> delegate = self.bridge.delegate;
- if (![delegate respondsToSelector:@selector(bridge:didNotFindModule:)]) {
- return @{};
- }
- NSString *moduleName = name;
- BOOL result = [delegate bridge:self.bridge didNotFindModule:moduleName];
- if (!result) {
- moduleName = [name stringByAppendingString:@"Manager"];
- result = [delegate bridge:self.bridge didNotFindModule:moduleName];
- }
- if (!result) {
- return @{};
- }
- id module = [self.bridge moduleForName:moduleName lazilyLoadIfNecessary:RCTTurboModuleEnabled()];
- if (module == nil) {
- // There is all sorts of code in this codebase that drops prefixes.
- //
- // If we didn't find a module, it's possible because it's stored under a key
- // which had RCT Prefixes stripped. Lets check one more time...
- module = [self.bridge moduleForName:RCTDropReactPrefixes(moduleName) lazilyLoadIfNecessary:RCTTurboModuleEnabled()];
- }
- if (!module) {
- return @{};
- }
- RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:[module class] bridge:self.bridge];
- _componentDataByName[componentData.name] = componentData;
- NSMutableDictionary *directEvents = [NSMutableDictionary new];
- NSMutableDictionary *bubblingEvents = [NSMutableDictionary new];
- NSMutableDictionary<NSString *, id> *moduleConstants =
- moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
- return @{
- @"viewConfig" : moduleConstants,
- };
- }
- RCT_EXPORT_METHOD(configureNextLayoutAnimation
- : (NSDictionary *)config withCallback
- : (RCTResponseSenderBlock)callback errorCallback
- : (__unused RCTResponseSenderBlock)errorCallback)
- {
- RCTLayoutAnimationGroup *layoutAnimationGroup = [[RCTLayoutAnimationGroup alloc] initWithConfig:config
- callback:callback];
- [self addUIBlock:^(RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
- [uiManager setNextLayoutAnimationGroup:layoutAnimationGroup];
- }];
- }
- - (void)rootViewForReactTag:(NSNumber *)reactTag withCompletion:(void (^)(UIView *view))completion
- {
- RCTAssertMainQueue();
- RCTAssert(completion != nil, @"Attempted to resolve rootView for tag %@ without a completion block", reactTag);
- if (reactTag == nil) {
- completion(nil);
- return;
- }
- RCTExecuteOnUIManagerQueue(^{
- NSNumber *rootTag = [self shadowViewForReactTag:reactTag].rootView.reactTag;
- RCTExecuteOnMainQueue(^{
- UIView *rootView = nil;
- if (rootTag != nil) {
- rootView = [self viewForReactTag:rootTag];
- }
- completion(rootView);
- });
- });
- }
- static UIView *_jsResponder;
- + (UIView *)JSResponder
- {
- return _jsResponder;
- }
- @end
- @implementation RCTBridge (RCTUIManager)
- - (RCTUIManager *)uiManager
- {
- return [self moduleForClass:[RCTUIManager class]];
- }
- @end
|