From 56f2d76de9b05f077653ad7da8a00c1563470c30 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 14 May 2026 12:30:10 -0700 Subject: [PATCH] Remove legacy architecture code guarded by RCT_REMOVE_LEGACY_ARCH from RN iOS Summary: Changelog: [Internal] Remove legacy architecture code guarded by RCT_REMOVE_LEGACY_ARCH from RN iOS Differential Revision: D104495793 --- .../Libraries/AppDelegate/RCTAppSetupUtils.mm | 48 - .../AppDelegate/RCTReactNativeFactory.mm | 51 - .../Libraries/Image/RCTImageShadowView.h | 17 - .../Libraries/Image/RCTImageShadowView.mm | 28 - .../Libraries/Image/RCTImageView.h | 32 - .../Libraries/Image/RCTImageView.mm | 508 ------ .../Libraries/Image/RCTImageViewManager.h | 17 - .../Libraries/Image/RCTImageViewManager.mm | 115 -- .../Text/BaseText/RCTBaseTextShadowView.h | 35 - .../Text/BaseText/RCTBaseTextShadowView.mm | 162 -- .../Text/BaseText/RCTBaseTextViewManager.h | 13 - .../Text/BaseText/RCTBaseTextViewManager.mm | 56 - .../Libraries/Text/RCTTextAttributes.h | 95 -- .../Libraries/Text/RCTTextAttributes.mm | 350 ---- .../Text/RawText/RCTRawTextShadowView.h | 15 - .../Text/RawText/RCTRawTextShadowView.mm | 30 - .../Text/RawText/RCTRawTextViewManager.h | 13 - .../Text/RawText/RCTRawTextViewManager.mm | 24 - .../Libraries/Text/Text/RCTTextShadowView.h | 25 - .../Libraries/Text/Text/RCTTextShadowView.mm | 461 ----- .../Libraries/Text/Text/RCTTextView.h | 26 - .../Libraries/Text/Text/RCTTextView.mm | 290 ---- .../Libraries/Text/Text/RCTTextViewManager.h | 11 - .../Libraries/Text/Text/RCTTextViewManager.mm | 89 - .../Multiline/RCTMultilineTextInputView.h | 13 - .../Multiline/RCTMultilineTextInputView.mm | 59 - .../RCTMultilineTextInputViewManager.h | 13 - .../RCTMultilineTextInputViewManager.mm | 19 - .../TextInput/RCTBaseTextInputShadowView.h | 22 +- .../TextInput/RCTBaseTextInputShadowView.mm | 367 ---- .../Text/TextInput/RCTBaseTextInputView.h | 60 - .../Text/TextInput/RCTBaseTextInputView.mm | 841 --------- .../TextInput/RCTBaseTextInputViewManager.h | 9 - .../TextInput/RCTBaseTextInputViewManager.mm | 173 -- .../TextInput/RCTInputAccessoryShadowView.h | 9 - .../TextInput/RCTInputAccessoryShadowView.mm | 16 - .../Text/TextInput/RCTInputAccessoryView.h | 14 - .../Text/TextInput/RCTInputAccessoryView.mm | 74 - .../TextInput/RCTInputAccessoryViewContent.h | 9 - .../TextInput/RCTInputAccessoryViewContent.mm | 70 - .../TextInput/RCTInputAccessoryViewManager.h | 9 - .../TextInput/RCTInputAccessoryViewManager.mm | 33 - .../Singleline/RCTSinglelineTextInputView.h | 21 - .../Singleline/RCTSinglelineTextInputView.mm | 43 - .../RCTSinglelineTextInputViewManager.h | 21 - .../RCTSinglelineTextInputViewManager.mm | 35 - .../VirtualText/RCTVirtualTextShadowView.h | 17 - .../VirtualText/RCTVirtualTextShadowView.mm | 42 - .../Text/VirtualText/RCTVirtualTextView.h | 20 - .../Text/VirtualText/RCTVirtualTextView.mm | 8 - .../VirtualText/RCTVirtualTextViewManager.h | 9 - .../VirtualText/RCTVirtualTextViewManager.mm | 30 - .../Wrapper/Example/RCTWrapperExampleView.h | 13 - .../Wrapper/Example/RCTWrapperExampleView.m | 49 - .../Example/RCTWrapperExampleViewController.h | 13 - .../Example/RCTWrapperExampleViewController.m | 19 - .../RCTWrapperReactRootViewController.h | 17 - .../RCTWrapperReactRootViewController.m | 42 - .../Example/RCTWrapperReactRootViewManager.h | 15 - .../Example/RCTWrapperReactRootViewManager.m | 27 - .../Libraries/Wrapper/RCTWrapper.h | 65 - .../Libraries/Wrapper/RCTWrapperShadowView.h | 19 - .../Libraries/Wrapper/RCTWrapperShadowView.m | 130 -- .../Libraries/Wrapper/RCTWrapperView.h | 34 - .../Libraries/Wrapper/RCTWrapperView.m | 91 - .../RCTWrapperViewControllerHostingView.h | 22 - .../RCTWrapperViewControllerHostingView.m | 129 -- .../Libraries/Wrapper/RCTWrapperViewManager.h | 17 - .../Libraries/Wrapper/RCTWrapperViewManager.m | 23 - .../React/Base/RCTBridge+Private.h | 15 - packages/react-native/React/Base/RCTBridge.mm | 428 ----- .../React/Base/RCTBridgeDelegate.h | 66 - .../react-native/React/Base/RCTBridgeModule.h | 9 - .../react-native/React/Base/RCTBridgeProxy.mm | 13 - .../React/Base/RCTBundleManager.h | 3 - .../React/Base/RCTBundleManager.m | 31 - .../React/Base/RCTCallableJSModules.m | 18 - .../React/Base/RCTJavaScriptExecutor.h | 69 - .../react-native/React/Base/RCTModuleData.mm | 484 ------ .../React/Base/RCTModuleRegistry.m | 25 - .../React/Base/RCTRootContentView.h | 35 - .../React/Base/RCTRootContentView.m | 103 -- .../react-native/React/Base/RCTRootView.m | 373 ---- .../React/Base/RCTRootViewInternal.h | 20 - .../react-native/React/Base/RCTViewRegistry.m | 30 +- .../React/Base/Surface/RCTSurface.mm | 582 ------- .../Base/Surface/RCTSurfaceRootShadowView.m | 89 - .../React/CoreModules/RCTLogBox.mm | 11 - .../React/CoreModules/RCTLogBoxView.h | 3 - .../React/CoreModules/RCTLogBoxView.mm | 28 - .../React/CxxBridge/RCTCxxBridge.mm | 1511 ----------------- .../RCTJSIExecutorRuntimeInstaller.h | 17 - .../RCTJSIExecutorRuntimeInstaller.mm | 26 - .../React/CxxBridge/RCTObjcExecutor.h | 28 - .../React/CxxBridge/RCTObjcExecutor.mm | 142 -- .../CxxModule/DispatchMessageQueueThread.h | 39 - .../React/CxxModule/RCTCxxUtils.h | 8 - .../React/CxxModule/RCTCxxUtils.mm | 14 - .../React/CxxModule/RCTNativeModule.h | 25 - .../React/CxxModule/RCTNativeModule.mm | 233 --- .../React/Modules/RCTUIManager.mm | 1497 ---------------- .../React/Views/RCTActivityIndicatorView.h | 8 - .../React/Views/RCTActivityIndicatorView.m | 18 - .../Views/RCTActivityIndicatorViewManager.h | 15 - .../Views/RCTActivityIndicatorViewManager.m | 57 - .../React/Views/RCTDebuggingOverlayManager.h | 9 - .../React/Views/RCTDebuggingOverlayManager.m | 59 - .../React/Views/RCTModalHostView.h | 56 - .../React/Views/RCTModalHostView.m | 256 --- .../React/Views/RCTModalHostViewController.h | 15 - .../React/Views/RCTModalHostViewController.m | 66 - .../React/Views/RCTModalHostViewManager.h | 24 - .../React/Views/RCTModalHostViewManager.m | 123 -- .../React/Views/RCTModalManager.h | 14 - .../React/Views/RCTModalManager.m | 38 - .../React/Views/RCTRootShadowView.m | 39 - .../react-native/React/Views/RCTShadowView.m | 723 -------- packages/react-native/React/Views/RCTSwitch.h | 14 - packages/react-native/React/Views/RCTSwitch.m | 16 - .../React/Views/RCTSwitchManager.h | 9 - .../React/Views/RCTSwitchManager.m | 62 - .../Views/RefreshControl/RCTRefreshControl.h | 16 - .../Views/RefreshControl/RCTRefreshControl.m | 221 --- .../RefreshControl/RCTRefreshControlManager.h | 9 - .../RefreshControl/RCTRefreshControlManager.m | 39 - .../SafeAreaView/RCTSafeAreaShadowView.h | 13 - .../SafeAreaView/RCTSafeAreaShadowView.m | 49 - .../Views/SafeAreaView/RCTSafeAreaView.h | 19 - .../Views/SafeAreaView/RCTSafeAreaView.m | 71 - .../SafeAreaView/RCTSafeAreaViewLocalData.h | 17 - .../SafeAreaView/RCTSafeAreaViewLocalData.m | 17 - .../SafeAreaView/RCTSafeAreaViewManager.h | 13 - .../SafeAreaView/RCTSafeAreaViewManager.m | 24 - .../ScrollView/RCTScrollContentShadowView.h | 11 - .../ScrollView/RCTScrollContentShadowView.m | 27 - .../Views/ScrollView/RCTScrollContentView.h | 11 - .../Views/ScrollView/RCTScrollContentView.m | 28 - .../ScrollView/RCTScrollContentViewManager.h | 9 - .../ScrollView/RCTScrollContentViewManager.m | 23 - .../React/Views/ScrollView/RCTScrollView.h | 81 - .../React/Views/ScrollView/RCTScrollView.m | 1160 ------------- .../Views/ScrollView/RCTScrollViewManager.h | 16 - .../Views/ScrollView/RCTScrollViewManager.m | 194 --- 143 files changed, 2 insertions(+), 14954 deletions(-) delete mode 100644 packages/react-native/Libraries/Image/RCTImageShadowView.h delete mode 100644 packages/react-native/Libraries/Image/RCTImageShadowView.mm delete mode 100644 packages/react-native/Libraries/Image/RCTImageView.h delete mode 100644 packages/react-native/Libraries/Image/RCTImageView.mm delete mode 100644 packages/react-native/Libraries/Image/RCTImageViewManager.h delete mode 100644 packages/react-native/Libraries/Image/RCTImageViewManager.mm delete mode 100644 packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.h delete mode 100644 packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.mm delete mode 100644 packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm delete mode 100644 packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.mm delete mode 100644 packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h delete mode 100644 packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.mm delete mode 100644 packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h delete mode 100644 packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm delete mode 100644 packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.h delete mode 100644 packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.mm delete mode 100644 packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.mm diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm index fabfff326b8..cb34841283b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm @@ -120,60 +120,12 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) RCTTurboModuleManager *turboModuleManager, const std::shared_ptr &runtimeScheduler) { -#ifndef RCT_REMOVE_LEGACY_ARCH - // Necessary to allow NativeModules to lookup TurboModules - [bridge setRCTTurboModuleRegistry:turboModuleManager]; - -#if RCT_DEV - /** - * Instantiating DevMenu has the side-effect of registering - * shortcuts for CMD + d, CMD + i, and CMD + n via RCTDevMenu. - * Therefore, when TurboModules are enabled, we must manually create this - * NativeModule. - */ - [turboModuleManager moduleForName:"RCTDevMenu"]; -#endif // end RCT_DEV - - auto runtimeInstallerLambda = [turboModuleManager, bridge, runtimeScheduler](facebook::jsi::Runtime &runtime) { - if (!bridge || !turboModuleManager) { - return; - } - if (runtimeScheduler) { - facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler); - } - [turboModuleManager installJSBindings:runtime]; - }; -#if USE_THIRD_PARTY_JSC != 1 - return std::make_unique( - facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda)); -#endif -#else - // This method should not be invoked in the New Arch. So when Legacy Arch is removed, we can - // safly return a nullptr. return nullptr; -#endif } std::unique_ptr RCTAppSetupJsExecutorFactoryForOldArch( RCTBridge *bridge, const std::shared_ptr &runtimeScheduler) { -#ifndef RCT_REMOVE_LEGACY_ARCH - auto runtimeInstallerLambda = [bridge, runtimeScheduler](facebook::jsi::Runtime &runtime) { - if (!bridge) { - return; - } - if (runtimeScheduler) { - facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler); - } - }; -#if USE_THIRD_PARTY_JSC != 1 - return std::make_unique( - facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda)); -#endif -#else - // This method should not be invoked in the New Arch. So when Legacy Arch is removed, we can - // safly return a nullptr. return nullptr; -#endif } diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm index c1cc9d7d22f..a537ca15fd3 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm @@ -274,12 +274,7 @@ - (RCTRootViewFactory *)createRCTRootViewFactory configuration.sourceURLForBridge = ^NSURL *_Nullable(RCTBridge *_Nonnull bridge) { -#ifndef RCT_REMOVE_LEGACY_ARCH - return [weakSelf.delegate sourceURLForBridge:bridge]; -#else - // When the Legacy Arch is removed, the Delegate does not have a sourceURLForBridge method return [weakSelf.delegate bundleURL]; -#endif }; if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) { @@ -289,52 +284,6 @@ - (RCTRootViewFactory *)createRCTRootViewFactory }; } -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a extraLazyModuleClassesForBridge method - if ([self.delegate respondsToSelector:@selector(extraLazyModuleClassesForBridge:)]) { - configuration.extraLazyModuleClassesForBridge = - ^NSDictionary *_Nonnull(RCTBridge *_Nonnull bridge) - { - return [weakSelf.delegate extraLazyModuleClassesForBridge:bridge]; - }; - } -#endif - -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a bridge:didNotFindModule method - // We return NO, because if we have invoked this method is unlikely that the module will be actually registered - if ([self.delegate respondsToSelector:@selector(bridge:didNotFindModule:)]) { - configuration.bridgeDidNotFindModule = ^BOOL(RCTBridge *_Nonnull bridge, NSString *_Nonnull moduleName) { - return [weakSelf.delegate bridge:bridge didNotFindModule:moduleName]; - }; - } -#endif - -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a - // loadSourceForBridge:onProgress:onComplete: method - // We then call the loadBundleAtURL:onProgress:onComplete: instead - if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) { - configuration.loadSourceForBridgeWithProgress = - ^(RCTBridge *_Nonnull bridge, - RCTSourceLoadProgressBlock _Nonnull onProgress, - RCTSourceLoadBlock _Nonnull loadCallback) { - [weakSelf.delegate loadSourceForBridge:bridge onProgress:onProgress onComplete:loadCallback]; - }; - } -#endif - -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a - // loadSourceForBridge:withBlock: method - // We then call the loadBundleAtURL:onProgress:onComplete: instead - if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) { - configuration.loadSourceForBridge = ^(RCTBridge *_Nonnull bridge, RCTSourceLoadBlock _Nonnull loadCallback) { - [weakSelf.delegate loadSourceForBridge:bridge withBlock:loadCallback]; - }; - } -#endif - configuration.jsRuntimeConfiguratorDelegate = self; return [[RCTRootViewFactory alloc] initWithTurboModuleDelegate:self hostDelegate:self configuration:configuration]; diff --git a/packages/react-native/Libraries/Image/RCTImageShadowView.h b/packages/react-native/Libraries/Image/RCTImageShadowView.h deleted file mode 100644 index 4a23a5a08a1..00000000000 --- a/packages/react-native/Libraries/Image/RCTImageShadowView.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTImageShadowView : RCTShadowView - -@end - -#endif diff --git a/packages/react-native/Libraries/Image/RCTImageShadowView.mm b/packages/react-native/Libraries/Image/RCTImageShadowView.mm deleted file mode 100644 index f8b1fb29243..00000000000 --- a/packages/react-native/Libraries/Image/RCTImageShadowView.mm +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTImageShadowView - -- (BOOL)isYogaLeafNode -{ - return YES; -} - -- (BOOL)canHaveSubviews -{ - return NO; -} - -@end - -#endif diff --git a/packages/react-native/Libraries/Image/RCTImageView.h b/packages/react-native/Libraries/Image/RCTImageView.h deleted file mode 100644 index 035171f3de8..00000000000 --- a/packages/react-native/Libraries/Image/RCTImageView.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import -#import -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@class RCTBridge; -@class RCTImageSource; - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTImageView : RCTView - -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - -@property (nonatomic, assign) UIEdgeInsets capInsets; -@property (nonatomic, strong) UIImage *defaultImage; -@property (nonatomic, assign) UIImageRenderingMode renderingMode; -@property (nonatomic, copy) NSArray *imageSources; -@property (nonatomic, assign) CGFloat blurRadius; -@property (nonatomic, assign) RCTResizeMode resizeMode; -@property (nonatomic, copy) NSString *internal_analyticTag; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Image/RCTImageView.mm b/packages/react-native/Libraries/Image/RCTImageView.mm deleted file mode 100644 index 0d07075a21d..00000000000 --- a/packages/react-native/Libraries/Image/RCTImageView.mm +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -/** - * Determines whether an image of `currentSize` should be reloaded for display - * at `idealSize`. - */ -static BOOL RCTShouldReloadImageForSizeChange(CGSize currentSize, CGSize idealSize) -{ - static const CGFloat upscaleThreshold = 1.2; - static const CGFloat downscaleThreshold = 0.5; - - CGFloat widthMultiplier = idealSize.width / currentSize.width; - CGFloat heightMultiplier = idealSize.height / currentSize.height; - - return widthMultiplier > upscaleThreshold || widthMultiplier < downscaleThreshold || - heightMultiplier > upscaleThreshold || heightMultiplier < downscaleThreshold; -} - -/** - * See RCTConvert (ImageSource). We want to send down the source as a similar - * JSON parameter. - */ -static NSDictionary *onLoadParamsForSource(RCTImageSource *source) -{ - NSDictionary *dict = @{ - @"uri" : source.request.URL.absoluteString, - @"width" : @(source.size.width * source.scale), - @"height" : @(source.size.height * source.scale), - }; - return @{@"source" : dict}; -} - -@interface RCTImageView () - -@property (nonatomic, copy) RCTDirectEventBlock onLoadStart; -@property (nonatomic, copy) RCTDirectEventBlock onProgress; -@property (nonatomic, copy) RCTDirectEventBlock onError; -@property (nonatomic, copy) RCTDirectEventBlock onPartialLoad; -@property (nonatomic, copy) RCTDirectEventBlock onLoad; -@property (nonatomic, copy) RCTDirectEventBlock onLoadEnd; - -@end - -@implementation RCTImageView { - // Weak reference back to the bridge, for image loading - __weak RCTBridge *_bridge; - - // Weak reference back to the active image loader. - __weak id _imageLoader; - - // The image source that's currently displayed - RCTImageSource *_imageSource; - - // The image source that's being loaded from the network - RCTImageSource *_pendingImageSource; - - // Size of the image loaded / being loaded, so we can determine when to issue a reload to accommodate a changing size. - CGSize _targetSize; - - // Whether the latest change of props requires the image to be reloaded - BOOL _needsReload; - - RCTUIImageViewAnimated *_imageView; - - RCTImageURLLoaderRequest *_loaderRequest; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if ((self = [super initWithFrame:CGRectZero])) { - _bridge = bridge; - _imageView = [RCTUIImageViewAnimated new]; - _imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [self addSubview:_imageView]; - - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserver:self - selector:@selector(clearImageIfDetached) - name:UIApplicationDidReceiveMemoryWarningNotification - object:nil]; - [center addObserver:self - selector:@selector(clearImageIfDetached) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; - [center addObserver:self - selector:@selector(clearImageIfDetached) - - name:UISceneDidEnterBackgroundNotification - object:nil]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)init) - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) - -- (void)updateWithImage:(UIImage *)image -{ - if (!image) { - _imageView.image = nil; - return; - } - - // Apply rendering mode - if (_renderingMode != image.renderingMode) { - image = [image imageWithRenderingMode:_renderingMode]; - } - - if (_resizeMode == RCTResizeModeRepeat) { - image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeTile]; - } else if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _capInsets)) { - // Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired - image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch]; - } - - // Apply trilinear filtering to smooth out missized images - _imageView.layer.minificationFilter = kCAFilterTrilinear; - _imageView.layer.magnificationFilter = kCAFilterTrilinear; - - _imageView.image = image; -} - -- (void)setImage:(UIImage *)image -{ - image = image ?: _defaultImage; - if (image != self.image) { - [self updateWithImage:image]; - } -} - -- (UIImage *)image -{ - return _imageView.image; -} - -- (void)setBlurRadius:(CGFloat)blurRadius -{ - if (blurRadius != _blurRadius) { - _blurRadius = blurRadius; - _needsReload = YES; - } -} - -- (void)setCapInsets:(UIEdgeInsets)capInsets -{ - if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) { - if (UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero) || - UIEdgeInsetsEqualToEdgeInsets(capInsets, UIEdgeInsetsZero)) { - _capInsets = capInsets; - // Need to reload image when enabling or disabling capInsets - _needsReload = YES; - } else { - _capInsets = capInsets; - [self updateWithImage:self.image]; - } - } -} - -- (void)setRenderingMode:(UIImageRenderingMode)renderingMode -{ - if (_renderingMode != renderingMode) { - _renderingMode = renderingMode; - [self updateWithImage:self.image]; - } -} - -- (void)setImageSources:(NSArray *)imageSources -{ - if (![imageSources isEqual:_imageSources]) { - _imageSources = [imageSources copy]; - _needsReload = YES; - } -} - -- (void)setResizeMode:(RCTResizeMode)resizeMode -{ - if (_resizeMode != resizeMode) { - _resizeMode = resizeMode; - - if (_resizeMode == RCTResizeModeRepeat) { - // Repeat resize mode is handled by the UIImage. Use scale to fill - // so the repeated image fills the UIImageView. - _imageView.contentMode = UIViewContentModeScaleToFill; - } else { - _imageView.contentMode = (UIViewContentMode)resizeMode; - } - - if ([self shouldReloadImageSourceAfterResize]) { - _needsReload = YES; - } - } -} - -- (void)setInternal_analyticTag:(NSString *)internal_analyticTag -{ - if (_internal_analyticTag != internal_analyticTag) { - _internal_analyticTag = internal_analyticTag; - _needsReload = YES; - } -} - -- (void)cancelImageLoad -{ - [_loaderRequest cancel]; - _pendingImageSource = nil; -} - -- (void)cancelAndClearImageLoad -{ - [self cancelImageLoad]; - - [_imageLoader trackURLImageRequestDidDestroy:_loaderRequest]; - _loaderRequest = nil; - - if (!self.image) { - self.image = _defaultImage; - } -} - -- (void)clearImageIfDetached -{ - if (!self.window) { - [self cancelAndClearImageLoad]; - self.image = nil; - _imageSource = nil; - } -} - -- (BOOL)hasMultipleSources -{ - return _imageSources.count > 1; -} - -- (RCTImageSource *)imageSourceForSize:(CGSize)size -{ - if (![self hasMultipleSources]) { - return _imageSources.firstObject; - } - - // Need to wait for layout pass before deciding. - if (CGSizeEqualToSize(size, CGSizeZero)) { - return nil; - } - - const CGFloat scale = RCTScreenScale(); - const CGFloat targetImagePixels = size.width * size.height * scale * scale; - - RCTImageSource *bestSource = nil; - CGFloat bestFit = CGFLOAT_MAX; - for (RCTImageSource *source in _imageSources) { - CGSize imgSize = source.size; - const CGFloat imagePixels = imgSize.width * imgSize.height * source.scale * source.scale; - const CGFloat fit = ABS(1 - (imagePixels / targetImagePixels)); - - if (fit < bestFit) { - bestFit = fit; - bestSource = source; - } - } - return bestSource; -} - -- (BOOL)shouldReloadImageSourceAfterResize -{ - // If capInsets are set, image doesn't need reloading when resized - return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero); -} - -- (BOOL)shouldChangeImageSource -{ - // We need to reload if the desired image source is different from the current image - // source AND the image load that's pending - RCTImageSource *desiredImageSource = [self imageSourceForSize:self.frame.size]; - return ![desiredImageSource isEqual:_imageSource] && ![desiredImageSource isEqual:_pendingImageSource]; -} - -- (void)reloadImage -{ - [self cancelAndClearImageLoad]; - _needsReload = NO; - - RCTImageSource *source = [self imageSourceForSize:self.frame.size]; - _pendingImageSource = source; - - if (source && self.frame.size.width > 0 && self.frame.size.height > 0) { - if (_onLoadStart) { - _onLoadStart(nil); - } - - RCTImageLoaderProgressBlock progressHandler = nil; - if (self.onProgress) { - RCTDirectEventBlock onProgress = self.onProgress; - progressHandler = ^(int64_t loaded, int64_t total) { - onProgress(@{ - @"loaded" : @((double)loaded), - @"total" : @((double)total), - }); - }; - } - - __weak RCTImageView *weakSelf = self; - RCTImageLoaderPartialLoadBlock partialLoadHandler = ^(UIImage *image) { - [weakSelf imageLoaderLoadedImage:image error:nil forImageSource:source partial:YES]; - }; - - CGSize imageSize = self.bounds.size; - CGFloat imageScale = RCTScreenScale(); - if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) { - // Don't resize images that use capInsets - imageSize = CGSizeZero; - imageScale = source.scale; - } - - RCTImageLoaderCompletionBlockWithMetadata completionHandler = ^(NSError *error, UIImage *loadedImage, id metadata) { - [weakSelf imageLoaderLoadedImage:loadedImage error:error forImageSource:source partial:NO]; - }; - - if (!_imageLoader) { - _imageLoader = [_bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES]; - } - - RCTImageURLLoaderRequest *loaderRequest = - [_imageLoader loadImageWithURLRequest:source.request - size:imageSize - scale:imageScale - clipped:NO - resizeMode:_resizeMode - priority:RCTImageLoaderPriorityImmediate - attribution:{.nativeViewTag = [self.reactTag intValue], - .surfaceId = [self.rootTag intValue], - .analyticTag = self.internal_analyticTag} - progressBlock:progressHandler - partialLoadBlock:partialLoadHandler - completionBlock:completionHandler]; - _loaderRequest = loaderRequest; - } else { - [self cancelAndClearImageLoad]; - } -} - -- (void)imageLoaderLoadedImage:(UIImage *)loadedImage - error:(NSError *)error - forImageSource:(RCTImageSource *)source - partial:(BOOL)isPartialLoad -{ - if (![source isEqual:_pendingImageSource]) { - // Bail out if source has changed since we started loading - return; - } - - if (error) { - __weak RCTImageView *weakSelf = self; - RCTExecuteOnMainQueue(^{ - weakSelf.image = nil; - }); - - if (_onError) { - _onError(@{ - @"error" : error.localizedDescription, - @"responseCode" : (error.userInfo[@"httpStatusCode"] ?: [NSNull null]), - @"httpResponseHeaders" : (error.userInfo[@"httpResponseHeaders"] ?: [NSNull null]) - }); - } - if (_onLoadEnd) { - _onLoadEnd(nil); - } - return; - } - - __weak RCTImageView *weakSelf = self; - void (^setImageBlock)(UIImage *) = ^(UIImage *image) { - RCTImageView *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - if (!isPartialLoad) { - strongSelf->_imageSource = source; - strongSelf->_pendingImageSource = nil; - } - - strongSelf.image = image; - - if (isPartialLoad) { - if (strongSelf->_onPartialLoad) { - strongSelf->_onPartialLoad(nil); - } - } else { - if (strongSelf->_onLoad) { - RCTImageSource *sourceLoaded = [source imageSourceWithSize:image.size scale:image.scale]; - strongSelf->_onLoad(onLoadParamsForSource(sourceLoaded)); - } - if (strongSelf->_onLoadEnd) { - strongSelf->_onLoadEnd(nil); - } - } - }; - - if (_blurRadius > __FLT_EPSILON__) { - // Blur on a background thread to avoid blocking interaction - CGFloat blurRadius = self.blurRadius; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - UIImage *blurredImage = RCTBlurredImageWithRadius(loadedImage, blurRadius); - RCTExecuteOnMainQueue(^{ - setImageBlock(blurredImage); - }); - }); - } else { - // No blur, so try to set the image on the main thread synchronously to minimize image - // flashing. (For instance, if this view gets attached to a window, then -didMoveToWindow - // calls -reloadImage, and we want to set the image synchronously if possible so that the - // image property is set in the same CATransaction that attaches this view to the window.) - RCTExecuteOnMainQueue(^{ - setImageBlock(loadedImage); - }); - } -} - -- (void)reactSetFrame:(CGRect)frame -{ - [super reactSetFrame:frame]; - - // If we didn't load an image yet, or the new frame triggers a different image source - // to be loaded, reload to swap to the proper image source. - if ([self shouldChangeImageSource]) { - _targetSize = frame.size; - [self reloadImage]; - } else if ([self shouldReloadImageSourceAfterResize]) { - CGSize imageSize = self.image.size; - CGFloat imageScale = self.image.scale; - CGSize idealSize = RCTTargetSize( - imageSize, imageScale, frame.size, RCTScreenScale(), RCTResizeModeFromUIViewContentMode(self.contentMode), YES); - - // Don't reload if the current image or target image size is close enough - if (!RCTShouldReloadImageForSizeChange(imageSize, idealSize) || - !RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) { - return; - } - - // Don't reload if the current image size is the maximum size of either the pending image source or image source - CGSize imageSourceSize = (_imageSource ? _imageSource : _pendingImageSource).size; - if (imageSize.width * imageScale == imageSourceSize.width * _imageSource.scale && - imageSize.height * imageScale == imageSourceSize.height * _imageSource.scale) { - return; - } - - RCTLogInfo( - @"Reloading image %@ as size %@", _imageSource.request.URL.absoluteString, NSStringFromCGSize(idealSize)); - - // If the existing image or an image being loaded are not the right - // size, reload the asset in case there is a better size available. - _targetSize = idealSize; - [self reloadImage]; - } -} - -- (void)didSetProps:(NSArray *)changedProps -{ - if (_needsReload) { - [self reloadImage]; - } -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - - if (!self.window) { - // Cancel loading the image if we've moved offscreen. In addition to helping - // prioritise image requests that are actually on-screen, this removes - // requests that have gotten "stuck" from the queue, unblocking other images - // from loading. - // Do not clear _loaderRequest because this component can be visible again without changing image source - [self cancelImageLoad]; - } else if ([self shouldChangeImageSource]) { - [self reloadImage]; - } -} - -- (void)dealloc -{ - [_imageLoader trackURLImageDidDestroy:_loaderRequest]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Image/RCTImageViewManager.h b/packages/react-native/Libraries/Image/RCTImageViewManager.h deleted file mode 100644 index b824e979add..00000000000 --- a/packages/react-native/Libraries/Image/RCTImageViewManager.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTImageViewManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Image/RCTImageViewManager.mm b/packages/react-native/Libraries/Image/RCTImageViewManager.mm deleted file mode 100644 index 74e1713b820..00000000000 --- a/packages/react-native/Libraries/Image/RCTImageViewManager.mm +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import -#import - -#import -#import -#import - -@implementation RCTImageViewManager - -RCT_EXPORT_MODULE() - -- (RCTShadowView *)shadowView -{ - return [RCTImageShadowView new]; -} - -- (UIView *)view -{ - return [[RCTImageView alloc] initWithBridge:self.bridge]; -} - -RCT_EXPORT_VIEW_PROPERTY(blurRadius, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) -RCT_REMAP_VIEW_PROPERTY(defaultSource, defaultImage, UIImage) -RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onPartialLoad, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(resizeMode, RCTResizeMode) -RCT_EXPORT_VIEW_PROPERTY(internal_analyticTag, NSString) -RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray); -RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView) -{ - // Default tintColor isn't nil - it's inherited from the superView - but we - // want to treat a null json value for `tintColor` as meaning 'disable tint', - // so we toggle `renderingMode` here instead of in `-[RCTImageView setTintColor:]` - view.tintColor = [RCTConvert UIColor:json] ?: defaultView.tintColor; - view.renderingMode = json ? UIImageRenderingModeAlwaysTemplate : defaultView.renderingMode; -} - -RCT_EXPORT_METHOD( - getSize : (NSURLRequest *)request successBlock : (RCTResponseSenderBlock) - successBlock errorBlock : (RCTResponseErrorBlock)errorBlock) -{ - [[self.bridge moduleForName:@"ImageLoader" - lazilyLoadIfNecessary:YES] getImageSizeForURLRequest:request - block:^(NSError *error, CGSize size) { - if (error) { - errorBlock(error); - } else { - successBlock(@[ @(size.width), @(size.height) ]); - } - }]; -} - -RCT_EXPORT_METHOD( - getSizeWithHeaders : (RCTImageSource *)source resolve : (RCTPromiseResolveBlock) - resolve reject : (RCTPromiseRejectBlock)reject) -{ - [[self.bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES] - getImageSizeForURLRequest:source.request - block:^(NSError *error, CGSize size) { - if (error) { - reject(@"E_GET_SIZE_FAILURE", nil, error); - return; - } - resolve(@{@"width" : @(size.width), @"height" : @(size.height)}); - }]; -} - -RCT_EXPORT_METHOD( - prefetchImage : (NSURLRequest *)request resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock) - reject) -{ - if (!request) { - reject(@"E_INVALID_URI", @"Cannot prefetch an image for an empty URI", nil); - return; - } - id imageLoader = (id)[self.bridge moduleForName:@"ImageLoader" - lazilyLoadIfNecessary:YES]; - [imageLoader loadImageWithURLRequest:request - priority:RCTImageLoaderPriorityPrefetch - callback:^(NSError *error, UIImage *image) { - if (error) { - reject(@"E_PREFETCH_FAILURE", nil, error); - return; - } - resolve(@YES); - }]; -} - -RCT_EXPORT_METHOD( - queryCache : (NSArray *)requests resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) -{ - resolve([[self.bridge moduleForName:@"ImageLoader"] getImageCacheStatus:requests]); -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.h b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.h deleted file mode 100644 index 0bccf7604cd..00000000000 --- a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTTextAttributes.h" - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName - __attribute__((deprecated("This API will be removed along with the legacy architecture."))); - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTBaseTextShadowView : RCTShadowView { - @protected - NSAttributedString *_Nullable cachedAttributedText; - @protected - RCTTextAttributes *_Nullable cachedTextAttributes; -} - -@property (nonatomic, strong) RCTTextAttributes *textAttributes; - -- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.mm b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.mm deleted file mode 100644 index 2d8efc0507d..00000000000 --- a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.mm +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import -#import - -NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName = - @"RCTBaseTextShadowViewEmbeddedShadowViewAttributeName"; - -static void RCTInlineViewYogaNodeDirtied(YGNodeConstRef node) -{ - // An inline view (a view nested inside of a text node) does not have a parent - // in the Yoga tree. Consequently, we have to manually propagate the inline - // view's dirty signal up through the text nodes. At some point, it'll reach - // the outermost text node which has a Yoga node and then Yoga will take over - // the dirty signal propagation. - RCTShadowView *inlineView = (__bridge RCTShadowView *)YGNodeGetContext(node); - RCTBaseTextShadowView *baseTextShadowView = (RCTBaseTextShadowView *)inlineView.reactSuperview; - - [baseTextShadowView dirtyLayout]; -} - -@implementation RCTBaseTextShadowView - -- (instancetype)init -{ - if (self = [super init]) { - _textAttributes = [RCTTextAttributes new]; - } - - return self; -} - -- (void)setReactTag:(NSNumber *)reactTag -{ - [super setReactTag:reactTag]; - _textAttributes.tag = reactTag; -} - -#pragma mark - Life Cycle - -- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)index -{ - [super insertReactSubview:subview atIndex:index]; - - [self dirtyLayout]; - - if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) { - YGNodeSetDirtiedFunc(subview.yogaNode, RCTInlineViewYogaNodeDirtied); - } -} - -- (void)removeReactSubview:(RCTShadowView *)subview -{ - if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) { - YGNodeSetDirtiedFunc(subview.yogaNode, NULL); - } - - [self dirtyLayout]; - - [super removeReactSubview:subview]; -} - -#pragma mark - attributedString - -- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes -{ - RCTTextAttributes *textAttributes; - - if (baseTextAttributes) { - textAttributes = [baseTextAttributes copy]; - [textAttributes applyTextAttributes:self.textAttributes]; - } else { - textAttributes = [self.textAttributes copy]; - } - - if (cachedAttributedText && [cachedTextAttributes isEqual:textAttributes]) { - return cachedAttributedText; - } - - NSMutableAttributedString *attributedText = [NSMutableAttributedString new]; - - [attributedText beginEditing]; - - for (RCTShadowView *shadowView in self.reactSubviews) { - // Special Case: RCTRawTextShadowView - if ([shadowView isKindOfClass:[RCTRawTextShadowView class]]) { - RCTRawTextShadowView *rawTextShadowView = (RCTRawTextShadowView *)shadowView; - NSString *text = rawTextShadowView.text; - if (text) { - NSAttributedString *rawTextAttributedString = - [[NSAttributedString alloc] initWithString:[textAttributes applyTextAttributesToText:text] - attributes:textAttributes.effectiveTextAttributes]; - [attributedText appendAttributedString:rawTextAttributedString]; - } - continue; - } - - // Special Case: RCTBaseTextShadowView - if ([shadowView isKindOfClass:[RCTBaseTextShadowView class]]) { - RCTBaseTextShadowView *baseTextShadowView = (RCTBaseTextShadowView *)shadowView; - NSAttributedString *baseTextAttributedString = - [baseTextShadowView attributedTextWithBaseTextAttributes:textAttributes]; - [attributedText appendAttributedString:baseTextAttributedString]; - continue; - } - - // Generic Case: Any RCTShadowView - NSTextAttachment *attachment = [NSTextAttachment new]; - NSMutableAttributedString *embeddedShadowViewAttributedString = [NSMutableAttributedString new]; - [embeddedShadowViewAttributedString beginEditing]; - [embeddedShadowViewAttributedString - appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; - [embeddedShadowViewAttributedString addAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName - value:shadowView - range:(NSRange){0, embeddedShadowViewAttributedString.length}]; - [embeddedShadowViewAttributedString endEditing]; - [attributedText appendAttributedString:embeddedShadowViewAttributedString]; - } - - [attributedText endEditing]; - - [self clearLayout]; - - cachedAttributedText = [attributedText copy]; - cachedTextAttributes = textAttributes; - - return cachedAttributedText; -} - -- (void)dirtyLayout -{ - [super dirtyLayout]; - cachedAttributedText = nil; - cachedTextAttributes = nil; -} - -- (void)didUpdateReactSubviews -{ - [super didUpdateReactSubviews]; - [self dirtyLayout]; -} - -- (void)didSetProps:(NSArray *)changedProps -{ - [super didSetProps:changedProps]; - [self dirtyLayout]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.h b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.h index b3e568d1c65..b6bc9e2e3c2 100644 --- a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.h +++ b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.h @@ -6,16 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTBaseTextViewManager : RCTViewManager - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm index 4843a9bd4db..08b2d4804d0 100644 --- a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm +++ b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm @@ -6,59 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTBaseTextViewManager - -RCT_EXPORT_MODULE(RCTBaseText) - -- (UIView *)view -{ - RCTAssert(NO, @"The `-[RCTBaseTextViewManager view]` property must be overridden in subclass."); - return nil; -} - -- (RCTShadowView *)shadowView -{ - RCTAssert(NO, @"The `-[RCTBaseTextViewManager shadowView]` property must be overridden in subclass."); - return nil; -} - -#pragma mark - Text Attributes - -// Color -RCT_REMAP_SHADOW_PROPERTY(color, textAttributes.foregroundColor, UIColor) -RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textAttributes.backgroundColor, UIColor) -RCT_REMAP_SHADOW_PROPERTY(opacity, textAttributes.opacity, CGFloat) -// Font -RCT_REMAP_SHADOW_PROPERTY(fontFamily, textAttributes.fontFamily, NSString) -RCT_REMAP_SHADOW_PROPERTY(fontSize, textAttributes.fontSize, CGFloat) -RCT_REMAP_SHADOW_PROPERTY(fontWeight, textAttributes.fontWeight, NSString) -RCT_REMAP_SHADOW_PROPERTY(fontStyle, textAttributes.fontStyle, NSString) -RCT_REMAP_SHADOW_PROPERTY(fontVariant, textAttributes.fontVariant, NSArray) -RCT_REMAP_SHADOW_PROPERTY(allowFontScaling, textAttributes.allowFontScaling, BOOL) -RCT_REMAP_SHADOW_PROPERTY(dynamicTypeRamp, textAttributes.dynamicTypeRamp, RCTDynamicTypeRamp) -RCT_REMAP_SHADOW_PROPERTY(maxFontSizeMultiplier, textAttributes.maxFontSizeMultiplier, CGFloat) -RCT_REMAP_SHADOW_PROPERTY(letterSpacing, textAttributes.letterSpacing, CGFloat) -// Paragraph Styles -RCT_REMAP_SHADOW_PROPERTY(lineHeight, textAttributes.lineHeight, CGFloat) -RCT_REMAP_SHADOW_PROPERTY(textAlign, textAttributes.alignment, NSTextAlignment) -RCT_REMAP_SHADOW_PROPERTY(writingDirection, textAttributes.baseWritingDirection, NSWritingDirection) -RCT_REMAP_SHADOW_PROPERTY(lineBreakStrategyIOS, textAttributes.lineBreakStrategy, NSLineBreakStrategy) -RCT_REMAP_SHADOW_PROPERTY(lineBreakModeIOS, textAttributes.lineBreakMode, NSLineBreakMode) -// Decoration -RCT_REMAP_SHADOW_PROPERTY(textDecorationColor, textAttributes.textDecorationColor, UIColor) -RCT_REMAP_SHADOW_PROPERTY(textDecorationStyle, textAttributes.textDecorationStyle, NSUnderlineStyle) -RCT_REMAP_SHADOW_PROPERTY(textDecorationLine, textAttributes.textDecorationLine, RCTTextDecorationLineType) -// Shadow -RCT_REMAP_SHADOW_PROPERTY(textShadowOffset, textAttributes.textShadowOffset, CGSize) -RCT_REMAP_SHADOW_PROPERTY(textShadowRadius, textAttributes.textShadowRadius, CGFloat) -RCT_REMAP_SHADOW_PROPERTY(textShadowColor, textAttributes.textShadowColor, UIColor) -// Special -RCT_REMAP_SHADOW_PROPERTY(isHighlighted, textAttributes.isHighlighted, BOOL) -RCT_REMAP_SHADOW_PROPERTY(textTransform, textAttributes.textTransform, RCTTextTransform) - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/RCTTextAttributes.h b/packages/react-native/Libraries/Text/RCTTextAttributes.h index f19eff0b90a..f8f6ca1dde0 100644 --- a/packages/react-native/Libraries/Text/RCTTextAttributes.h +++ b/packages/react-native/Libraries/Text/RCTTextAttributes.h @@ -10,98 +10,3 @@ #import #import #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const RCTTextAttributesIsHighlightedAttributeName - __attribute__((deprecated("This API will be removed along with the legacy architecture."))); -extern NSString *const RCTTextAttributesTagAttributeName - __attribute__((deprecated("This API will be removed along with the legacy architecture."))); - -/** - * Represents knowledge about all supported *text* attributes - * assigned to some text component such as , , - * and . - */ -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTTextAttributes : NSObject - -// Color -@property (nonatomic, strong, nullable) UIColor *foregroundColor; -@property (nonatomic, strong, nullable) UIColor *backgroundColor; -@property (nonatomic, assign) CGFloat opacity; -// Font -@property (nonatomic, copy, nullable) NSString *fontFamily; -@property (nonatomic, assign) CGFloat fontSize; -@property (nonatomic, assign) CGFloat fontSizeMultiplier; -@property (nonatomic, assign) CGFloat maxFontSizeMultiplier; -@property (nonatomic, copy, nullable) NSString *fontWeight; -@property (nonatomic, copy, nullable) NSString *fontStyle; -@property (nonatomic, copy, nullable) NSArray *fontVariant; -@property (nonatomic, assign) BOOL allowFontScaling; -@property (nonatomic, assign) RCTDynamicTypeRamp dynamicTypeRamp; -@property (nonatomic, assign) CGFloat letterSpacing; -// Paragraph Styles -@property (nonatomic, assign) CGFloat lineHeight; -@property (nonatomic, assign) NSTextAlignment alignment; -@property (nonatomic, assign) NSWritingDirection baseWritingDirection; -@property (nonatomic, assign) NSLineBreakStrategy lineBreakStrategy; -@property (nonatomic, assign) NSLineBreakMode lineBreakMode; -// Decoration -@property (nonatomic, strong, nullable) UIColor *textDecorationColor; -@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle; -@property (nonatomic, assign) RCTTextDecorationLineType textDecorationLine; -// Shadow -@property (nonatomic, assign) CGSize textShadowOffset; -@property (nonatomic, assign) CGFloat textShadowRadius; -@property (nonatomic, strong, nullable) UIColor *textShadowColor; -// Special -@property (nonatomic, assign) BOOL isHighlighted; -@property (nonatomic, strong, nullable) NSNumber *tag; -@property (nonatomic, assign) UIUserInterfaceLayoutDirection layoutDirection; -@property (nonatomic, assign) RCTTextTransform textTransform; - -#pragma mark - Inheritance - -- (void)applyTextAttributes:(RCTTextAttributes *)textAttributes; - -#pragma mark - Adapters - -/** - * Text attributes in NSAttributedString terms. - */ -- (NSDictionary *)effectiveTextAttributes; - -/** - * Constructed paragraph style. - */ -- (NSParagraphStyle *_Nullable)effectiveParagraphStyle; - -/** - * Constructed font. - */ -- (UIFont *)effectiveFont; - -/** - * Font size multiplier reflects `allowFontScaling`, `fontSizeMultiplier`, and `maxFontSizeMultiplier`. - */ -- (CGFloat)effectiveFontSizeMultiplier; - -/** - * Foreground and background colors with opacity and right defaults. - */ -- (UIColor *)effectiveForegroundColor; -- (UIColor *)effectiveBackgroundColor; - -/** - * Text transformed per 'none', 'uppercase', 'lowercase', 'capitalize' - */ -- (NSString *)applyTextAttributesToText:(NSString *)text; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/RCTTextAttributes.mm b/packages/react-native/Libraries/Text/RCTTextAttributes.mm index 430987aa7df..bfeedfef004 100644 --- a/packages/react-native/Libraries/Text/RCTTextAttributes.mm +++ b/packages/react-native/Libraries/Text/RCTTextAttributes.mm @@ -6,353 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -NSString *const RCTTextAttributesIsHighlightedAttributeName = @"RCTTextAttributesIsHighlightedAttributeName"; -NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttributeName"; - -@implementation RCTTextAttributes - -- (instancetype)init -{ - if (self = [super init]) { - _fontSize = NAN; - _letterSpacing = NAN; - _lineHeight = NAN; - _textDecorationStyle = NSUnderlineStyleSingle; - _fontSizeMultiplier = NAN; - _maxFontSizeMultiplier = NAN; - _alignment = NSTextAlignmentNatural; - _baseWritingDirection = NSWritingDirectionNatural; - _lineBreakStrategy = NSLineBreakStrategyNone; - _lineBreakMode = NSLineBreakByWordWrapping; - _textShadowRadius = NAN; - _opacity = NAN; - _textTransform = RCTTextTransformUndefined; - } - - return self; -} - -- (void)applyTextAttributes:(RCTTextAttributes *)textAttributes -{ - // Note: All lines marked with `*` does not use explicit/correct rules to compare old and new values because - // their types do not have special designated value representing undefined/unspecified/inherit meaning. - // We will address this in the future. - - // Color - _foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor; - _backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor; - _opacity = - !isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity; - - // Font - _fontFamily = textAttributes->_fontFamily ?: _fontFamily; - _fontSize = !isnan(textAttributes->_fontSize) ? textAttributes->_fontSize : _fontSize; - _fontSizeMultiplier = - !isnan(textAttributes->_fontSizeMultiplier) ? textAttributes->_fontSizeMultiplier : _fontSizeMultiplier; - _maxFontSizeMultiplier = - !isnan(textAttributes->_maxFontSizeMultiplier) ? textAttributes->_maxFontSizeMultiplier : _maxFontSizeMultiplier; - _fontWeight = textAttributes->_fontWeight ?: _fontWeight; - _fontStyle = textAttributes->_fontStyle ?: _fontStyle; - _fontVariant = textAttributes->_fontVariant ?: _fontVariant; - _allowFontScaling = textAttributes->_allowFontScaling || _allowFontScaling; // * - _dynamicTypeRamp = textAttributes->_dynamicTypeRamp != RCTDynamicTypeRampUndefined ? textAttributes->_dynamicTypeRamp - : _dynamicTypeRamp; - _letterSpacing = !isnan(textAttributes->_letterSpacing) ? textAttributes->_letterSpacing : _letterSpacing; - - // Paragraph Styles - _lineHeight = !isnan(textAttributes->_lineHeight) ? textAttributes->_lineHeight : _lineHeight; - _alignment = textAttributes->_alignment != NSTextAlignmentNatural ? textAttributes->_alignment : _alignment; // * - _baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural - ? textAttributes->_baseWritingDirection - : _baseWritingDirection; // * - _lineBreakStrategy = textAttributes->_lineBreakStrategy ?: _lineBreakStrategy; - _lineBreakMode = textAttributes->_lineBreakMode ?: _lineBreakMode; - - // Decoration - _textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor; - _textDecorationStyle = textAttributes->_textDecorationStyle != NSUnderlineStyleSingle - ? textAttributes->_textDecorationStyle - : _textDecorationStyle; // * - _textDecorationLine = textAttributes->_textDecorationLine != RCTTextDecorationLineTypeNone - ? textAttributes->_textDecorationLine - : _textDecorationLine; // * - - // Shadow - _textShadowOffset = !CGSizeEqualToSize(textAttributes->_textShadowOffset, CGSizeZero) - ? textAttributes->_textShadowOffset - : _textShadowOffset; // * - _textShadowRadius = !isnan(textAttributes->_textShadowRadius) ? textAttributes->_textShadowRadius : _textShadowRadius; - _textShadowColor = textAttributes->_textShadowColor ?: _textShadowColor; - - // Special - _isHighlighted = textAttributes->_isHighlighted || _isHighlighted; // * - _tag = textAttributes->_tag ?: _tag; - _layoutDirection = textAttributes->_layoutDirection != UIUserInterfaceLayoutDirectionLeftToRight - ? textAttributes->_layoutDirection - : _layoutDirection; - _textTransform = - textAttributes->_textTransform != RCTTextTransformUndefined ? textAttributes->_textTransform : _textTransform; -} - -- (NSParagraphStyle *)effectiveParagraphStyle -{ - // Paragraph Style - NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; - BOOL isParagraphStyleUsed = NO; - if (_alignment != NSTextAlignmentNatural) { - NSTextAlignment alignment = _alignment; - if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { - if (alignment == NSTextAlignmentRight) { - alignment = NSTextAlignmentLeft; - } else if (alignment == NSTextAlignmentLeft) { - alignment = NSTextAlignmentRight; - } - } - - paragraphStyle.alignment = alignment; - isParagraphStyleUsed = YES; - } - - if (_baseWritingDirection != NSWritingDirectionNatural) { - paragraphStyle.baseWritingDirection = _baseWritingDirection; - isParagraphStyleUsed = YES; - } - - if (_lineBreakStrategy != NSLineBreakStrategyNone) { - if (@available(iOS 14.0, *)) { - paragraphStyle.lineBreakStrategy = _lineBreakStrategy; - isParagraphStyleUsed = YES; - } - } - - if (_lineBreakMode != NSLineBreakByWordWrapping) { - paragraphStyle.lineBreakMode = _lineBreakMode; - isParagraphStyleUsed = YES; - } - - if (!isnan(_lineHeight)) { - CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier; - paragraphStyle.minimumLineHeight = lineHeight; - paragraphStyle.maximumLineHeight = lineHeight; - isParagraphStyleUsed = YES; - } - - if (isParagraphStyleUsed) { - return [paragraphStyle copy]; - } - - return nil; -} - -- (NSDictionary *)effectiveTextAttributes -{ - NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:10]; - - // Font - UIFont *font = self.effectiveFont; - if (font) { - attributes[NSFontAttributeName] = font; - } - - // Colors - UIColor *effectiveForegroundColor = self.effectiveForegroundColor; - - if (_foregroundColor || !isnan(_opacity)) { - attributes[NSForegroundColorAttributeName] = effectiveForegroundColor; - } - - if (_backgroundColor || !isnan(_opacity)) { - attributes[NSBackgroundColorAttributeName] = self.effectiveBackgroundColor; - } - - // Kerning - if (!isnan(_letterSpacing)) { - attributes[NSKernAttributeName] = @(_letterSpacing); - } - - // Paragraph Style - NSParagraphStyle *paragraphStyle = [self effectiveParagraphStyle]; - if (paragraphStyle) { - attributes[NSParagraphStyleAttributeName] = paragraphStyle; - } - - // Decoration - BOOL isTextDecorationEnabled = NO; - if (_textDecorationLine == RCTTextDecorationLineTypeUnderline || - _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) { - isTextDecorationEnabled = YES; - attributes[NSUnderlineStyleAttributeName] = @(_textDecorationStyle); - } - - if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough || - _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) { - isTextDecorationEnabled = YES; - attributes[NSStrikethroughStyleAttributeName] = @(_textDecorationStyle); - } - - if (_textDecorationColor || isTextDecorationEnabled) { - attributes[NSStrikethroughColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor; - attributes[NSUnderlineColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor; - } - - // Shadow - if (!isnan(_textShadowRadius)) { - NSShadow *shadow = [NSShadow new]; - shadow.shadowOffset = _textShadowOffset; - shadow.shadowBlurRadius = _textShadowRadius; - shadow.shadowColor = _textShadowColor; - attributes[NSShadowAttributeName] = shadow; - } - - // Special - if (_isHighlighted) { - attributes[RCTTextAttributesIsHighlightedAttributeName] = @YES; - } - - if (_tag) { - attributes[RCTTextAttributesTagAttributeName] = _tag; - } - - return [attributes copy]; -} - -- (UIFont *)effectiveFont -{ - // FIXME: RCTFont has thread-safety issues and must be rewritten. - return [RCTFont updateFont:nil - withFamily:_fontFamily - size:@(isnan(_fontSize) ? 0 : _fontSize) - weight:_fontWeight - style:_fontStyle - variant:_fontVariant - scaleMultiplier:self.effectiveFontSizeMultiplier]; -} - -- (CGFloat)effectiveFontSizeMultiplier -{ - bool fontScalingEnabled = !RCTHasFontHandlerSet() && _allowFontScaling; - - if (fontScalingEnabled) { - CGFloat fontSizeMultiplier = !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0; - if (_dynamicTypeRamp != RCTDynamicTypeRampUndefined) { - UIFontMetrics *fontMetrics = RCTUIFontMetricsForDynamicTypeRamp(_dynamicTypeRamp); - // Using a specific font size reduces rounding errors from -scaledValueForValue: - CGFloat requestedSize = isnan(_fontSize) ? RCTBaseSizeForDynamicTypeRamp(_dynamicTypeRamp) : _fontSize; - fontSizeMultiplier = [fontMetrics scaledValueForValue:requestedSize] / requestedSize; - } - CGFloat maxFontSizeMultiplier = !isnan(_maxFontSizeMultiplier) ? _maxFontSizeMultiplier : 0.0; - return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; - } else { - return 1.0; - } -} - -- (UIColor *)effectiveForegroundColor -{ - UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor]; - - if (!isnan(_opacity)) { - effectiveForegroundColor = - [effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity]; - } - - return effectiveForegroundColor; -} - -- (UIColor *)effectiveBackgroundColor -{ - UIColor *effectiveBackgroundColor = _backgroundColor; // ?: [[UIColor whiteColor] colorWithAlphaComponent:0]; - - if (effectiveBackgroundColor && !isnan(_opacity)) { - effectiveBackgroundColor = - [effectiveBackgroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveBackgroundColor.CGColor) * _opacity]; - } - - return effectiveBackgroundColor ?: [UIColor clearColor]; -} - -static NSString *capitalizeText(NSString *text) -{ - NSMutableString *result = [[NSMutableString alloc] initWithString:text]; - [result - enumerateSubstringsInRange:NSMakeRange(0, text.length) - options:NSStringEnumerationByWords - usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - [result replaceCharactersInRange:NSMakeRange(substringRange.location, 1) - withString:[[substring substringToIndex:1] uppercaseString]]; - }]; - return result; -} - -- (NSString *)applyTextAttributesToText:(NSString *)text -{ - switch (_textTransform) { - case RCTTextTransformUndefined: - case RCTTextTransformNone: - return text; - case RCTTextTransformLowercase: - return [text lowercaseString]; - case RCTTextTransformUppercase: - return [text uppercaseString]; - case RCTTextTransformCapitalize: - return capitalizeText(text); - } -} - -- (RCTTextAttributes *)copyWithZone:(NSZone *)zone -{ - RCTTextAttributes *textAttributes = [RCTTextAttributes new]; - [textAttributes applyTextAttributes:self]; - return textAttributes; -} - -#pragma mark - NSObject - -- (BOOL)isEqual:(RCTTextAttributes *)textAttributes -{ - if (!textAttributes) { - return NO; - } - if (self == textAttributes) { - return YES; - } - -#define RCTTextAttributesCompareFloats(a) ((a == textAttributes->a) || (isnan(a) && isnan(textAttributes->a))) -#define RCTTextAttributesCompareSize(a) CGSizeEqualToSize(a, textAttributes->a) -#define RCTTextAttributesCompareObjects(a) ((a == textAttributes->a) || [a isEqual:textAttributes->a]) -#define RCTTextAttributesCompareStrings(a) ((a == textAttributes->a) || [a isEqualToString:textAttributes->a]) -#define RCTTextAttributesCompareOthers(a) (a == textAttributes->a) - - return RCTTextAttributesCompareObjects(_foregroundColor) && RCTTextAttributesCompareObjects(_backgroundColor) && - RCTTextAttributesCompareFloats(_opacity) && - // Font - RCTTextAttributesCompareObjects(_fontFamily) && RCTTextAttributesCompareFloats(_fontSize) && - RCTTextAttributesCompareFloats(_fontSizeMultiplier) && RCTTextAttributesCompareFloats(_maxFontSizeMultiplier) && - RCTTextAttributesCompareStrings(_fontWeight) && RCTTextAttributesCompareObjects(_fontStyle) && - RCTTextAttributesCompareObjects(_fontVariant) && RCTTextAttributesCompareOthers(_allowFontScaling) && - RCTTextAttributesCompareOthers(_dynamicTypeRamp) && RCTTextAttributesCompareFloats(_letterSpacing) && - // Paragraph Styles - RCTTextAttributesCompareFloats(_lineHeight) && RCTTextAttributesCompareFloats(_alignment) && - RCTTextAttributesCompareOthers(_baseWritingDirection) && RCTTextAttributesCompareOthers(_lineBreakStrategy) && - RCTTextAttributesCompareOthers(_lineBreakMode) && - // Decoration - RCTTextAttributesCompareObjects(_textDecorationColor) && RCTTextAttributesCompareOthers(_textDecorationStyle) && - RCTTextAttributesCompareOthers(_textDecorationLine) && - // Shadow - RCTTextAttributesCompareSize(_textShadowOffset) && RCTTextAttributesCompareFloats(_textShadowRadius) && - RCTTextAttributesCompareObjects(_textShadowColor) && - // Special - RCTTextAttributesCompareOthers(_isHighlighted) && RCTTextAttributesCompareObjects(_tag) && - RCTTextAttributesCompareOthers(_layoutDirection) && RCTTextAttributesCompareOthers(_textTransform); -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.h b/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.h index 50d1a3c8211..103dec8dfad 100644 --- a/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.h +++ b/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.h @@ -6,18 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTRawTextShadowView : RCTShadowView - -@property (nonatomic, copy, nullable) NSString *text; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.mm b/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.mm index 5d2aecaba19..dbd0d61f22d 100644 --- a/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.mm +++ b/packages/react-native/Libraries/Text/RawText/RCTRawTextShadowView.mm @@ -6,33 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -@implementation RCTRawTextShadowView - -- (void)setText:(NSString *)text -{ - if (_text != text && ![_text isEqualToString:text]) { - _text = [text copy]; - [self dirtyLayout]; - } -} - -- (void)dirtyLayout -{ - [self.superview dirtyLayout]; -} - -- (NSString *)description -{ - NSString *superDescription = super.description; - return [[superDescription substringToIndex:superDescription.length - 1] - stringByAppendingFormat:@"; text: %@>", self.text]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.h b/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.h index 35c182785a6..b6bc9e2e3c2 100644 --- a/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.h +++ b/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.h @@ -6,16 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTRawTextViewManager : RCTViewManager - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.mm b/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.mm index 15a851da28d..fb1aa350f22 100644 --- a/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.mm +++ b/packages/react-native/Libraries/Text/RawText/RCTRawTextViewManager.mm @@ -6,27 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -@implementation RCTRawTextViewManager - -RCT_EXPORT_MODULE(RCTRawText) - -- (UIView *)view -{ - return [UIView new]; -} - -- (RCTShadowView *)shadowView -{ - return [RCTRawTextShadowView new]; -} - -RCT_EXPORT_SHADOW_PROPERTY(text, NSString) - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.h b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.h index f3ff4b3447f..103dec8dfad 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.h +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.h @@ -6,28 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTBaseTextShadowView.h" - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTTextShadowView : RCTBaseTextShadowView - -- (instancetype)initWithBridge:(RCTBridge *)bridge; - -@property (nonatomic, assign) NSInteger maximumNumberOfLines; -@property (nonatomic, assign) NSLineBreakMode lineBreakMode; -@property (nonatomic, assign) BOOL adjustsFontSizeToFit; -@property (nonatomic, assign) CGFloat minimumFontScale; -@property (nonatomic, copy) RCTDirectEventBlock onTextLayout; - -- (void)uiManagerWillPerformMounting; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index 60de7af1370..3608c2b76bc 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -6,464 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import - -#import -#import "NSTextStorage+FontScaling.h" - -@implementation RCTTextShadowView { - __weak RCTBridge *_bridge; - BOOL _needsUpdateView; - NSMapTable *_cachedTextStorages; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super init]) { - _bridge = bridge; - _cachedTextStorages = [NSMapTable strongToStrongObjectsMapTable]; - _needsUpdateView = YES; - YGNodeSetMeasureFunc(self.yogaNode, RCTTextShadowViewMeasure); - YGNodeSetBaselineFunc(self.yogaNode, RCTTextShadowViewBaseline); - } - - return self; -} - -- (void)didSetProps:(NSArray *)changedProps -{ - [super didSetProps:changedProps]; - - // When applying a semi-transparent background color to Text component - // we must set the root text nodes text attribute background color to nil - // because the background color is drawn on the RCTTextView itself, as well - // as on the glphy background draw step. By setting this to nil, we allow - // the RCTTextView backgroundColor to be used, without affecting nested Text - // components. - self.textAttributes.backgroundColor = nil; - self.textAttributes.opacity = NAN; -} - -- (BOOL)isYogaLeafNode -{ - return YES; -} - -- (void)dirtyLayout -{ - [super dirtyLayout]; - YGNodeMarkDirty(self.yogaNode); - [self invalidateCache]; -} - -- (void)invalidateCache -{ - [_cachedTextStorages removeAllObjects]; - _needsUpdateView = YES; -} - -#pragma mark - RCTUIManagerObserver - -- (void)uiManagerWillPerformMounting -{ - if (YGNodeIsDirty(self.yogaNode)) { - return; - } - - if (!_needsUpdateView) { - return; - } - _needsUpdateView = NO; - - CGRect contentFrame = self.contentFrame; - NSTextStorage *textStorage = [self textStorageAndLayoutManagerThatFitsSize:self.contentFrame.size - exclusiveOwnership:YES]; - - NSNumber *tag = self.reactTag; - NSMutableArray *descendantViewTags = [NSMutableArray new]; - [textStorage enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName - inRange:NSMakeRange(0, textStorage.length) - options:0 - usingBlock:^(RCTShadowView *shadowView, NSRange range, __unused BOOL *stop) { - if (!shadowView) { - return; - } - - [descendantViewTags addObject:shadowView.reactTag]; - }]; - - [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTTextView *textView = (RCTTextView *)viewRegistry[tag]; - if (!textView) { - return; - } - - NSMutableArray *descendantViews = [NSMutableArray arrayWithCapacity:descendantViewTags.count]; - [descendantViewTags - enumerateObjectsUsingBlock:^(NSNumber *_Nonnull descendantViewTag, NSUInteger index, BOOL *_Nonnull stop) { - UIView *descendantView = viewRegistry[descendantViewTag]; - if (!descendantView) { - return; - } - - [descendantViews addObject:descendantView]; - }]; - - // Removing all references to Shadow Views to avoid unnecessary retaining. - [textStorage removeAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName - range:NSMakeRange(0, textStorage.length)]; - - [textView setTextStorage:textStorage contentFrame:contentFrame descendantViews:descendantViews]; - }]; -} - -- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText -{ - __block CGFloat maximumLineHeight = 0; - - [attributedText enumerateAttribute:NSParagraphStyleAttributeName - inRange:NSMakeRange(0, attributedText.length) - options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired - usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) { - if (!paragraphStyle) { - return; - } - - maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight); - }]; - - if (maximumLineHeight == 0) { - // `lineHeight` was not specified, nothing to do. - return; - } - - __block CGFloat maximumFontLineHeight = 0; - - [attributedText enumerateAttribute:NSFontAttributeName - inRange:NSMakeRange(0, attributedText.length) - options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired - usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { - if (!font) { - return; - } - - if (maximumFontLineHeight <= font.lineHeight) { - maximumFontLineHeight = font.lineHeight; - } - }]; - - if (maximumLineHeight < maximumFontLineHeight) { - return; - } - - CGFloat baseLineOffset = maximumLineHeight / 2.0 - maximumFontLineHeight / 2.0; - - [attributedText addAttribute:NSBaselineOffsetAttributeName - value:@(baseLineOffset) - range:NSMakeRange(0, attributedText.length)]; -} - -- (NSAttributedString *)attributedTextWithMeasuredAttachmentsThatFitSize:(CGSize)size -{ - static UIImage *placeholderImage; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - placeholderImage = [UIImage new]; - }); - - NSMutableAttributedString *attributedText = - [[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTextWithBaseTextAttributes:nil]]; - - [attributedText beginEditing]; - - [attributedText enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName - inRange:NSMakeRange(0, attributedText.length) - options:0 - usingBlock:^(RCTShadowView *shadowView, NSRange range, __unused BOOL *stop) { - if (!shadowView) { - return; - } - - CGSize fittingSize = [shadowView sizeThatFitsMinimumSize:CGSizeZero maximumSize:size]; - NSTextAttachment *attachment = [NSTextAttachment new]; - attachment.bounds = (CGRect){CGPointZero, fittingSize}; - attachment.image = placeholderImage; - [attributedText addAttribute:NSAttachmentAttributeName value:attachment range:range]; - }]; - - [attributedText endEditing]; - - return [attributedText copy]; -} - -- (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size exclusiveOwnership:(BOOL)exclusiveOwnership -{ - NSValue *key = [NSValue valueWithCGSize:size]; - NSTextStorage *cachedTextStorage = [_cachedTextStorages objectForKey:key]; - - if (cachedTextStorage) { - if (exclusiveOwnership) { - [_cachedTextStorages removeObjectForKey:key]; - } - - return cachedTextStorage; - } - - NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size]; - - textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. - textContainer.lineBreakMode = _maximumNumberOfLines > 0 ? _lineBreakMode : NSLineBreakByClipping; - textContainer.maximumNumberOfLines = _maximumNumberOfLines; - - NSLayoutManager *layoutManager = [NSLayoutManager new]; - layoutManager.usesFontLeading = NO; - [layoutManager addTextContainer:textContainer]; - - NSTextStorage *textStorage = - [[NSTextStorage alloc] initWithAttributedString:[self attributedTextWithMeasuredAttachmentsThatFitSize:size]]; - - [self postprocessAttributedText:textStorage]; - - [textStorage addLayoutManager:layoutManager]; - - if (_adjustsFontSizeToFit) { - CGFloat minimumFontSize = MAX(_minimumFontScale * (self.textAttributes.effectiveFont.pointSize), 4.0); - [textStorage scaleFontSizeToFitSize:size - minimumFontSize:minimumFontSize - maximumFontSize:self.textAttributes.effectiveFont.pointSize]; - } - - [self processTruncatedAttributedText:textStorage textContainer:textContainer layoutManager:layoutManager]; - - if (!exclusiveOwnership) { - [_cachedTextStorages setObject:textStorage forKey:key]; - } - - return textStorage; -} - -- (void)processTruncatedAttributedText:(NSTextStorage *)textStorage - textContainer:(NSTextContainer *)textContainer - layoutManager:(NSLayoutManager *)layoutManager -{ - if (_maximumNumberOfLines > 0) { - [layoutManager ensureLayoutForTextContainer:textContainer]; - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - __block int line = 0; - [layoutManager - enumerateLineFragmentsForGlyphRange:glyphRange - usingBlock:^( - CGRect rect, - CGRect usedRect, - NSTextContainer *_Nonnull _, - NSRange lineGlyphRange, - BOOL *_Nonnull stop) { - if (line == textContainer.maximumNumberOfLines - 1) { - NSRange truncatedRange = [layoutManager - truncatedGlyphRangeInLineFragmentForGlyphAtIndex:lineGlyphRange.location]; - - if (truncatedRange.location != NSNotFound) { - NSRange characterRange = - [layoutManager characterRangeForGlyphRange:truncatedRange - actualGlyphRange:nil]; - if (characterRange.location > 0 && characterRange.length > 0) { - // Remove color attributes for truncated range - for (NSAttributedStringKey key in - @[ NSForegroundColorAttributeName, NSBackgroundColorAttributeName ]) { - [textStorage removeAttribute:key range:characterRange]; - id attribute = [textStorage attribute:key - atIndex:characterRange.location - 1 - effectiveRange:nil]; - if (attribute) { - [textStorage addAttribute:key value:attribute range:characterRange]; - } - } - } - } - } - line++; - }]; - } -} - -- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics layoutContext:(RCTLayoutContext)layoutContext -{ - // If the view got new `contentFrame`, we have to redraw it because - // and sizes of embedded views may change. - if (!CGRectEqualToRect(self.layoutMetrics.contentFrame, layoutMetrics.contentFrame)) { - _needsUpdateView = YES; - } - - if (self.textAttributes.layoutDirection != layoutMetrics.layoutDirection) { - self.textAttributes.layoutDirection = layoutMetrics.layoutDirection; - [self invalidateCache]; - } - - [super layoutWithMetrics:layoutMetrics layoutContext:layoutContext]; -} - -- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext -{ - NSTextStorage *textStorage = [self textStorageAndLayoutManagerThatFitsSize:self.availableSize exclusiveOwnership:NO]; - NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; - NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - - [textStorage - enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName - inRange:characterRange - options:0 - usingBlock:^(RCTShadowView *shadowView, NSRange range, BOOL *stop) { - if (!shadowView) { - return; - } - - CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range inTextContainer:textContainer]; - - NSTextAttachment *attachment = [textStorage attribute:NSAttachmentAttributeName - atIndex:range.location - effectiveRange:nil]; - - CGSize attachmentSize = attachment.bounds.size; - - UIFont *font = [textStorage attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil]; - - CGRect frame = { - {RCTRoundPixelValue(glyphRect.origin.x), - RCTRoundPixelValue( - glyphRect.origin.y + glyphRect.size.height - attachmentSize.height + font.descender)}, - {RCTRoundPixelValue(attachmentSize.width), RCTRoundPixelValue(attachmentSize.height)}}; - - NSRange truncatedGlyphRange = - [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; - BOOL viewIsTruncated = NSIntersectionRange(range, truncatedGlyphRange).length != 0; - - RCTLayoutContext localLayoutContext = layoutContext; - localLayoutContext.absolutePosition.x += frame.origin.x; - localLayoutContext.absolutePosition.y += frame.origin.y; - - [shadowView layoutWithMinimumSize:{shadowView.minWidth.value, shadowView.minHeight.value} - maximumSize:frame.size - layoutDirection:self.layoutMetrics.layoutDirection - layoutContext:localLayoutContext]; - - RCTLayoutMetrics localLayoutMetrics = shadowView.layoutMetrics; - localLayoutMetrics.frame.origin = - frame.origin; // Reinforcing a proper frame origin for the Shadow View. - if (viewIsTruncated) { - localLayoutMetrics.displayType = RCTDisplayTypeNone; - } - [shadowView layoutWithMetrics:localLayoutMetrics layoutContext:localLayoutContext]; - }]; - - if (_onTextLayout) { - NSMutableArray *lineData = [NSMutableArray new]; - [layoutManager enumerateLineFragmentsForGlyphRange:glyphRange - usingBlock:^( - CGRect overallRect, - CGRect usedRect, - NSTextContainer *_Nonnull usedTextContainer, - NSRange lineGlyphRange, - BOOL *_Nonnull stop) { - NSRange range = [layoutManager characterRangeForGlyphRange:lineGlyphRange - actualGlyphRange:nil]; - NSString *renderedString = [textStorage.string substringWithRange:range]; - UIFont *font = [[textStorage attributedSubstringFromRange:range] - attribute:NSFontAttributeName - atIndex:0 - effectiveRange:nil]; - [lineData addObject:@{ - @"text" : renderedString, - @"x" : @(usedRect.origin.x), - @"y" : @(usedRect.origin.y), - @"width" : @(usedRect.size.width), - @"height" : @(usedRect.size.height), - @"descender" : @(-font.descender), - @"capHeight" : @(font.capHeight), - @"ascender" : @(font.ascender), - @"xHeight" : @(font.xHeight), - }]; - }]; - NSDictionary *payload = @{ - @"lines" : lineData, - }; - _onTextLayout(payload); - } -} - -- (CGFloat)lastBaselineForSize:(CGSize)size -{ - NSAttributedString *attributedText = [self textStorageAndLayoutManagerThatFitsSize:size exclusiveOwnership:NO]; - - __block CGFloat maximumDescender = 0.0; - - [attributedText enumerateAttribute:NSFontAttributeName - inRange:NSMakeRange(0, attributedText.length) - options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired - usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { - if (maximumDescender > font.descender) { - maximumDescender = font.descender; - } - }]; - - return size.height + maximumDescender; -} - -static YGSize RCTTextShadowViewMeasure( - YGNodeConstRef node, - float width, - YGMeasureMode widthMode, - float height, - YGMeasureMode heightMode) -{ - CGSize maximumSize = (CGSize){ - widthMode == YGMeasureModeUndefined ? CGFLOAT_MAX : RCTCoreGraphicsFloatFromYogaFloat(width), - heightMode == YGMeasureModeUndefined ? CGFLOAT_MAX : RCTCoreGraphicsFloatFromYogaFloat(height), - }; - - RCTTextShadowView *shadowTextView = (__bridge RCTTextShadowView *)YGNodeGetContext(node); - - NSTextStorage *textStorage = [shadowTextView textStorageAndLayoutManagerThatFitsSize:maximumSize - exclusiveOwnership:NO]; - - NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; - NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - [layoutManager ensureLayoutForTextContainer:textContainer]; - CGSize size = [layoutManager usedRectForTextContainer:textContainer].size; - - CGFloat letterSpacing = shadowTextView.textAttributes.letterSpacing; - if (!isnan(letterSpacing) && letterSpacing < 0) { - size.width -= letterSpacing; - } - - size = (CGSize){MIN(RCTCeilPixelValue(size.width), maximumSize.width), - MIN(RCTCeilPixelValue(size.height), maximumSize.height)}; - - // Adding epsilon value illuminates problems with converting values from - // `double` to `float`, and then rounding them to pixel grid in Yoga. - CGFloat epsilon = 0.001; - return (YGSize){RCTYogaFloatFromCoreGraphicsFloat(size.width + epsilon), - RCTYogaFloatFromCoreGraphicsFloat(size.height + epsilon)}; -} - -static float RCTTextShadowViewBaseline(YGNodeConstRef node, const float width, const float height) -{ - RCTTextShadowView *shadowTextView = (__bridge RCTTextShadowView *)YGNodeGetContext(node); - - CGSize size = (CGSize){RCTCoreGraphicsFloatFromYogaFloat(width), RCTCoreGraphicsFloatFromYogaFloat(height)}; - - CGFloat lastBaseline = [shadowTextView lastBaselineForSize:size]; - - return RCTYogaFloatFromCoreGraphicsFloat(lastBaseline); -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/Text/RCTTextView.h b/packages/react-native/Libraries/Text/Text/RCTTextView.h index 450cba2715a..41a8e64d9a5 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextView.h +++ b/packages/react-native/Libraries/Text/Text/RCTTextView.h @@ -6,29 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTTextView : UIView - -@property (nonatomic, assign) BOOL selectable; - -- (void)setTextStorage:(NSTextStorage *)textStorage - contentFrame:(CGRect)contentFrame - descendantViews:(NSArray *)descendantViews; - -/** - * (Experimental and unused for Paper) Pointer event handlers. - */ -@property (nonatomic, assign) RCTBubblingEventBlock onClick; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/Text/RCTTextView.mm b/packages/react-native/Libraries/Text/Text/RCTTextView.mm index ded7924b573..3c217b10f2c 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextView.mm @@ -6,293 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import -#import - -#import - -#import - -@interface RCTTextView () - -@property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0)); - -@end - -@implementation RCTTextView { - CAShapeLayer *_highlightLayer; - UILongPressGestureRecognizer *_longPressGestureRecognizer; - - NSArray *_Nullable _descendantViews; - NSTextStorage *_Nullable _textStorage; - CGRect _contentFrame; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - self.isAccessibilityElement = YES; - self.accessibilityTraits |= UIAccessibilityTraitStaticText; - self.opaque = NO; - self.contentMode = UIViewContentModeRedraw; - } - return self; -} - -- (NSString *)description -{ - NSString *stringToAppend = [NSString stringWithFormat:@" reactTag: %@; text: %@", self.reactTag, _textStorage.string]; - return [[super description] stringByAppendingString:stringToAppend]; -} - -- (void)setSelectable:(BOOL)selectable -{ - if (_selectable == selectable) { - return; - } - - _selectable = selectable; - - if (_selectable) { - [self enableContextMenu]; - } else { - [self disableContextMenu]; - } -} - -- (void)reactSetFrame:(CGRect)frame -{ - // Text looks super weird if its frame is animated. - // This disables the frame animation, without affecting opacity, etc. - [UIView performWithoutAnimation:^{ - [super reactSetFrame:frame]; - }]; -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as subviews are managed by `setTextStorage:` method -} - -- (void)setTextStorage:(NSTextStorage *)textStorage - contentFrame:(CGRect)contentFrame - descendantViews:(NSArray *)descendantViews -{ - _textStorage = textStorage; - _contentFrame = contentFrame; - - // FIXME: Optimize this. - for (UIView *view in _descendantViews) { - [view removeFromSuperview]; - } - - _descendantViews = descendantViews; - - for (UIView *view in descendantViews) { - [self addSubview:view]; - } - - [self setNeedsDisplay]; -} - -- (void)drawRect:(CGRect)rect -{ - [super drawRect:rect]; - if (!_textStorage) { - return; - } - - NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject; - NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - -#if TARGET_OS_MACCATALYST - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSaveGState(context); - // NSLayoutManager tries to draw text with sub-pixel anti-aliasing by default on - // macOS, but rendering SPAA onto a transparent background produces poor results. - // CATextLayer disables font smoothing by default now on macOS; we follow suit. - CGContextSetShouldSmoothFonts(context, NO); -#endif - - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin]; - [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin]; - - __block UIBezierPath *highlightPath = nil; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - [_textStorage - enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName - inRange:characterRange - options:0 - usingBlock:^(NSNumber *value, NSRange range, __unused BOOL *stop) { - if (!value.boolValue) { - return; - } - - [layoutManager - enumerateEnclosingRectsForGlyphRange:range - withinSelectedGlyphRange:range - inTextContainer:textContainer - usingBlock:^(CGRect enclosingRect, __unused BOOL *anotherStop) { - UIBezierPath *path = [UIBezierPath - bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) - cornerRadius:2]; - if (highlightPath) { - [highlightPath appendPath:path]; - } else { - highlightPath = path; - } - }]; - }]; - - if (highlightPath) { - if (!_highlightLayer) { - _highlightLayer = [CAShapeLayer layer]; - _highlightLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.25].CGColor; - [self.layer addSublayer:_highlightLayer]; - } - _highlightLayer.position = _contentFrame.origin; - _highlightLayer.path = highlightPath.CGPath; - } else { - [_highlightLayer removeFromSuperlayer]; - _highlightLayer = nil; - } - -#if TARGET_OS_MACCATALYST - CGContextRestoreGState(context); -#endif -} - -- (NSNumber *)reactTagAtPoint:(CGPoint)point -{ - NSNumber *reactTag = self.reactTag; - - CGFloat fraction; - NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject; - NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - NSUInteger characterIndex = [layoutManager characterIndexForPoint:point - inTextContainer:textContainer - fractionOfDistanceBetweenInsertionPoints:&fraction]; - - // If the point is not before (fraction == 0.0) the first character and not - // after (fraction == 1.0) the last character, then the attribute is valid. - if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && - (fraction < 1 || characterIndex < _textStorage.length - 1)) { - reactTag = [_textStorage attribute:RCTTextAttributesTagAttributeName atIndex:characterIndex effectiveRange:NULL]; - } - - return reactTag; -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - - if (!self.window) { - self.layer.contents = nil; - if (_highlightLayer) { - [_highlightLayer removeFromSuperlayer]; - _highlightLayer = nil; - } - } else if (_textStorage) { - [self setNeedsDisplay]; - } -} - -#pragma mark - Accessibility - -- (NSString *)accessibilityLabel -{ - NSString *superAccessibilityLabel = [super accessibilityLabel]; - if (superAccessibilityLabel) { - return superAccessibilityLabel; - } - return _textStorage.string; -} - -#pragma mark - Context Menu - -- (void)enableContextMenu -{ - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self - action:@selector(handleLongPress:)]; - if (@available(iOS 16.0, *)) { - _editMenuInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self]; - [self addInteraction:_editMenuInteraction]; - } - - [self addGestureRecognizer:_longPressGestureRecognizer]; -} - -- (void)disableContextMenu -{ - [self removeGestureRecognizer:_longPressGestureRecognizer]; - - if (@available(iOS 16.0, *)) { - [self removeInteraction:_editMenuInteraction]; - _editMenuInteraction = nil; - } - _longPressGestureRecognizer = nil; -} - -- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture -{ - if (@available(iOS 16.0, macCatalyst 16.0, *)) { - CGPoint location = [gesture locationInView:self]; - UIEditMenuConfiguration *config = [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:location]; - if (_editMenuInteraction) { - [_editMenuInteraction presentEditMenuWithConfiguration:config]; - } - } else { - UIMenuController *menuController = [UIMenuController sharedMenuController]; - - if (menuController.isMenuVisible) { - return; - } - - [menuController showMenuFromView:self rect:self.bounds]; - } -} - -- (BOOL)canBecomeFirstResponder -{ - return _selectable; -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - if (_selectable && action == @selector(copy:)) { - return YES; - } - - return [self.nextResponder canPerformAction:action withSender:sender]; -} - -- (void)copy:(id)sender -{ - NSAttributedString *attributedText = _textStorage; - - NSMutableDictionary *item = [NSMutableDictionary new]; - - NSData *rtf = [attributedText dataFromRange:NSMakeRange(0, attributedText.length) - documentAttributes:@{NSDocumentTypeDocumentAttribute : NSRTFDTextDocumentType} - error:nil]; - - if (rtf) { - [item setObject:rtf forKey:(id)kUTTypeFlatRTFD]; - } - - [item setObject:attributedText.string forKey:(id)kUTTypeUTF8PlainText]; - - UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; - pasteboard.items = @[ item ]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/Text/RCTTextViewManager.h b/packages/react-native/Libraries/Text/Text/RCTTextViewManager.h index 976393e61a3..b6bc9e2e3c2 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextViewManager.h +++ b/packages/react-native/Libraries/Text/Text/RCTTextViewManager.h @@ -6,14 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTBaseTextViewManager.h" - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTTextViewManager : RCTBaseTextViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/Text/RCTTextViewManager.mm b/packages/react-native/Libraries/Text/Text/RCTTextViewManager.mm index cf093baaf48..be9551dae02 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextViewManager.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextViewManager.mm @@ -6,92 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import -#import - -#import -#import - -@interface RCTTextViewManager () - -@end - -@implementation RCTTextViewManager { - NSHashTable *_shadowViews; -} - -RCT_EXPORT_MODULE(RCTText) - -RCT_REMAP_SHADOW_PROPERTY(numberOfLines, maximumNumberOfLines, NSInteger) -RCT_REMAP_SHADOW_PROPERTY(ellipsizeMode, lineBreakMode, NSLineBreakMode) -RCT_REMAP_SHADOW_PROPERTY(adjustsFontSizeToFit, adjustsFontSizeToFit, BOOL) -RCT_REMAP_SHADOW_PROPERTY(minimumFontScale, minimumFontScale, CGFloat) - -RCT_EXPORT_SHADOW_PROPERTY(onTextLayout, RCTDirectEventBlock) - -RCT_EXPORT_VIEW_PROPERTY(selectable, BOOL) - -- (void)setBridge:(RCTBridge *)bridge -{ - [super setBridge:bridge]; - _shadowViews = [NSHashTable weakObjectsHashTable]; - - [bridge.uiManager.observerCoordinator addObserver:self]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleDidUpdateMultiplierNotification) - name:@"RCTAccessibilityManagerDidUpdateMultiplierNotification" - object:[bridge moduleForName:@"AccessibilityManager" - lazilyLoadIfNecessary:YES]]; -} - -- (UIView *)view -{ - return [RCTTextView new]; -} - -- (RCTShadowView *)shadowView -{ - RCTTextShadowView *shadowView = [[RCTTextShadowView alloc] initWithBridge:self.bridge]; - shadowView.textAttributes.fontSizeMultiplier = - [[[self.bridge moduleForName:@"AccessibilityManager"] valueForKey:@"multiplier"] floatValue]; - [_shadowViews addObject:shadowView]; - return shadowView; -} - -#pragma mark - RCTUIManagerObserver - -- (void)uiManagerWillPerformMounting:(__unused RCTUIManager *)uiManager -{ - for (RCTTextShadowView *shadowView in _shadowViews) { - [shadowView uiManagerWillPerformMounting]; - } -} - -#pragma mark - Font Size Multiplier - -- (void)handleDidUpdateMultiplierNotification -{ - CGFloat fontSizeMultiplier = - [[[self.bridge moduleForName:@"AccessibilityManager"] valueForKey:@"multiplier"] floatValue]; - - NSHashTable *shadowViews = _shadowViews; - RCTExecuteOnUIManagerQueue(^{ - for (RCTTextShadowView *shadowView in shadowViews) { - shadowView.textAttributes.fontSizeMultiplier = fontSizeMultiplier; - [shadowView dirtyLayout]; - } - - [self.bridge.uiManager setNeedsLayout]; - }); -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h index dbe5c917c04..35ea22441eb 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h @@ -6,16 +6,3 @@ */ #import "RCTBaseTextInputView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTMultilineTextInputView : RCTBaseTextInputView - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.mm index 2cfe185446f..033d9735f12 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.mm +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.mm @@ -6,62 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import - -@implementation RCTMultilineTextInputView { - RCTUITextView *_backedTextInputView; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super initWithBridge:bridge]) { - _backedTextInputView = [[RCTUITextView alloc] initWithFrame:self.bounds]; - _backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _backedTextInputView.textInputDelegate = self; - - [self addSubview:_backedTextInputView]; - } - - return self; -} - -- (id)backedTextInputView -{ - return _backedTextInputView; -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - RCTDirectEventBlock onScroll = self.onScroll; - - if (onScroll) { - CGPoint contentOffset = scrollView.contentOffset; - CGSize contentSize = scrollView.contentSize; - CGSize size = scrollView.bounds.size; - UIEdgeInsets contentInset = scrollView.contentInset; - - onScroll(@{ - @"contentOffset" : @{@"x" : @(contentOffset.x), @"y" : @(contentOffset.y)}, - @"contentInset" : @{ - @"top" : @(contentInset.top), - @"left" : @(contentInset.left), - @"bottom" : @(contentInset.bottom), - @"right" : @(contentInset.right) - }, - @"contentSize" : @{@"width" : @(contentSize.width), @"height" : @(contentSize.height)}, - @"layoutMeasurement" : @{@"width" : @(size.width), @"height" : @(size.height)}, - @"zoomScale" : @(scrollView.zoomScale ?: 1), - }); - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h index da4ac28ae07..611f39a5d54 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h @@ -6,16 +6,3 @@ */ #import "RCTBaseTextInputViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTMultilineTextInputViewManager : RCTBaseTextInputViewManager - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.mm b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.mm index cbf1433cca0..d85b38e1af6 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.mm +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.mm @@ -7,22 +7,3 @@ #import #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTMultilineTextInputViewManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge]; -} - -#pragma mark - Multiline (aka TextView) specific properties - -RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h index a0a21f6247b..87a74c02314 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h @@ -5,26 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -#import "RCTBaseTextShadowView.h" - #ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTBaseTextInputShadowView : RCTBaseTextShadowView - -- (instancetype)initWithBridge:(RCTBridge *)bridge; - -@property (nonatomic, copy, nullable) NSString *text; -@property (nonatomic, copy, nullable) NSString *placeholder; -@property (nonatomic, assign) NSInteger maximumNumberOfLines; -@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange; - -- (void)uiManagerWillPerformMounting; - -@end - -NS_ASSUME_NONNULL_END - +#import "RCTBaseTextShadowView.h" #endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm deleted file mode 100644 index 733e3acd251..00000000000 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import - -#import -#import "NSTextStorage+FontScaling.h" - -@implementation RCTBaseTextInputShadowView { - __weak RCTBridge *_bridge; - NSAttributedString *_Nullable _previousAttributedText; - BOOL _needsUpdateView; - NSAttributedString *_Nullable _localAttributedText; - CGSize _previousContentSize; - - NSString *_text; - NSTextStorage *_textStorage; - NSTextContainer *_textContainer; - NSLayoutManager *_layoutManager; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super init]) { - _bridge = bridge; - _needsUpdateView = YES; - - YGNodeSetMeasureFunc(self.yogaNode, RCTBaseTextInputShadowViewMeasure); - YGNodeSetBaselineFunc(self.yogaNode, RCTTextInputShadowViewBaseline); - } - - return self; -} - -- (BOOL)isYogaLeafNode -{ - return YES; -} - -- (void)didSetProps:(NSArray *)changedProps -{ - [super didSetProps:changedProps]; - - // `backgroundColor` and `opacity` are being applied directly to a UIView, - // therefore we need to exclude them from base `textAttributes`. - self.textAttributes.backgroundColor = nil; - self.textAttributes.opacity = NAN; -} - -- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext -{ - // Do nothing. -} - -- (void)setLocalData:(NSObject *)localData -{ - NSAttributedString *attributedText = (NSAttributedString *)localData; - - if ([attributedText isEqualToAttributedString:_localAttributedText]) { - return; - } - - _localAttributedText = attributedText; - [self dirtyLayout]; -} - -- (void)dirtyLayout -{ - [super dirtyLayout]; - _needsUpdateView = YES; - YGNodeMarkDirty(self.yogaNode); - [self invalidateContentSize]; -} - -- (void)invalidateContentSize -{ - if (!_onContentSizeChange) { - return; - } - - CGSize maximumSize = self.layoutMetrics.contentFrame.size; - - if (_maximumNumberOfLines == 1) { - maximumSize.width = CGFLOAT_MAX; - } else { - maximumSize.height = CGFLOAT_MAX; - } - - CGSize contentSize = [self sizeThatFitsMinimumSize:(CGSize)CGSizeZero maximumSize:maximumSize]; - - if (CGSizeEqualToSize(_previousContentSize, contentSize)) { - return; - } - _previousContentSize = contentSize; - - _onContentSizeChange(@{ - @"contentSize" : @{ - @"height" : @(contentSize.height), - @"width" : @(contentSize.width), - }, - @"target" : self.reactTag, - }); -} - -- (NSString *)text -{ - return _text; -} - -- (void)setText:(NSString *)text -{ - _text = text; - // Clear `_previousAttributedText` to notify the view about the change - // when `text` native prop is set. - _previousAttributedText = nil; - [self dirtyLayout]; -} - -#pragma mark - RCTUIManagerObserver - -- (void)uiManagerWillPerformMounting -{ - if (YGNodeIsDirty(self.yogaNode)) { - return; - } - - if (!_needsUpdateView) { - return; - } - _needsUpdateView = NO; - - UIEdgeInsets borderInsets = self.borderAsInsets; - UIEdgeInsets paddingInsets = self.paddingAsInsets; - - RCTTextAttributes *textAttributes = [self.textAttributes copy]; - - NSMutableAttributedString *attributedText = [[self attributedTextWithBaseTextAttributes:nil] mutableCopy]; - - // Removing all references to Shadow Views and tags to avoid unnecessary retaining - // and problems with comparing the strings. - [attributedText removeAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName - range:NSMakeRange(0, attributedText.length)]; - - [attributedText removeAttribute:RCTTextAttributesTagAttributeName range:NSMakeRange(0, attributedText.length)]; - - if (self.text.length) { - NSAttributedString *propertyAttributedText = - [[NSAttributedString alloc] initWithString:self.text attributes:self.textAttributes.effectiveTextAttributes]; - [attributedText insertAttributedString:propertyAttributedText atIndex:0]; - } - - [self postprocessAttributedText:attributedText]; - - NSAttributedString *newAttributedText; - if (![_previousAttributedText isEqualToAttributedString:attributedText]) { - // We have to follow `set prop` pattern: - // If the value has not changed, we must not notify the view about the change, - // otherwise we may break local (temporary) state of the text input. - newAttributedText = [attributedText copy]; - _previousAttributedText = newAttributedText; - } - - NSNumber *tag = self.reactTag; - - [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTBaseTextInputView *baseTextInputView = (RCTBaseTextInputView *)viewRegistry[tag]; - if (!baseTextInputView) { - return; - } - - baseTextInputView.textAttributes = textAttributes; - baseTextInputView.reactBorderInsets = borderInsets; - baseTextInputView.reactPaddingInsets = paddingInsets; - - if (newAttributedText) { - // Don't set `attributedText` if length equal to zero, otherwise it would shrink when attributes contain like - // `lineHeight`. - if (newAttributedText.length != 0) { - baseTextInputView.attributedText = newAttributedText; - } else { - baseTextInputView.attributedText = nil; - } - } - }]; -} - -- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText -{ - __block CGFloat maximumLineHeight = 0; - - [attributedText enumerateAttribute:NSParagraphStyleAttributeName - inRange:NSMakeRange(0, attributedText.length) - options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired - usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) { - if (!paragraphStyle) { - return; - } - - maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight); - }]; - - if (maximumLineHeight == 0) { - // `lineHeight` was not specified, nothing to do. - return; - } - - __block CGFloat maximumFontLineHeight = 0; - - [attributedText enumerateAttribute:NSFontAttributeName - inRange:NSMakeRange(0, attributedText.length) - options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired - usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { - if (!font) { - return; - } - - if (maximumFontLineHeight <= font.lineHeight) { - maximumFontLineHeight = font.lineHeight; - } - }]; - - if (maximumLineHeight < maximumFontLineHeight) { - return; - } - - CGFloat baseLineOffset = maximumLineHeight / 2.0 - maximumFontLineHeight / 2.0; - - [attributedText addAttribute:NSBaselineOffsetAttributeName - value:@(baseLineOffset) - range:NSMakeRange(0, attributedText.length)]; -} - -#pragma mark - - -- (NSAttributedString *)measurableAttributedText -{ - // Only for the very first render when we don't have `_localAttributedText`, - // we use value directly from the property and/or nested content. - NSAttributedString *attributedText = _localAttributedText ?: [self attributedTextWithBaseTextAttributes:nil]; - - if (attributedText.length == 0) { - // It's impossible to measure empty attributed string because all attributes are - // associated with some characters, so no characters means no data. - - // Placeholder also can represent the intrinsic size when it is visible. - NSString *text = self.placeholder; - if (!text.length) { - // Note: `zero-width space` is insufficient in some cases. - text = @"I"; - } - attributedText = [[NSAttributedString alloc] initWithString:text - attributes:self.textAttributes.effectiveTextAttributes]; - } - - return attributedText; -} - -- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize -{ - NSAttributedString *attributedText = [self measurableAttributedText]; - - if (!_textStorage) { - _textContainer = [NSTextContainer new]; - _textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. - _layoutManager = [NSLayoutManager new]; - [_layoutManager addTextContainer:_textContainer]; - _textStorage = [NSTextStorage new]; - [_textStorage addLayoutManager:_layoutManager]; - } - - _textContainer.size = maximumSize; - _textContainer.maximumNumberOfLines = _maximumNumberOfLines; - [_textStorage replaceCharactersInRange:(NSRange){0, _textStorage.length} withAttributedString:attributedText]; - [_layoutManager ensureLayoutForTextContainer:_textContainer]; - CGSize size = [_layoutManager usedRectForTextContainer:_textContainer].size; - - return (CGSize){MAX(minimumSize.width, MIN(RCTCeilPixelValue(size.width), maximumSize.width)), - MAX(minimumSize.height, MIN(RCTCeilPixelValue(size.height), maximumSize.height))}; -} - -- (CGFloat)lastBaselineForSize:(CGSize)size -{ - NSAttributedString *attributedText = [self measurableAttributedText]; - - __block CGFloat maximumDescender = 0.0; - - [attributedText enumerateAttribute:NSFontAttributeName - inRange:NSMakeRange(0, attributedText.length) - options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired - usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { - if (maximumDescender > font.descender) { - maximumDescender = font.descender; - } - }]; - - return size.height + maximumDescender; -} - -static YGSize RCTBaseTextInputShadowViewMeasure( - YGNodeConstRef node, - float width, - YGMeasureMode widthMode, - float height, - YGMeasureMode heightMode) -{ - RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node); - - CGSize minimumSize = CGSizeMake(0, 0); - CGSize maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); - - CGSize size = {RCTCoreGraphicsFloatFromYogaFloat(width), RCTCoreGraphicsFloatFromYogaFloat(height)}; - - switch (widthMode) { - case YGMeasureModeUndefined: - break; - case YGMeasureModeExactly: - minimumSize.width = size.width; - maximumSize.width = size.width; - break; - case YGMeasureModeAtMost: - maximumSize.width = size.width; - break; - } - - switch (heightMode) { - case YGMeasureModeUndefined: - break; - case YGMeasureModeExactly: - minimumSize.height = size.height; - maximumSize.height = size.height; - break; - case YGMeasureModeAtMost: - maximumSize.height = size.height; - break; - } - - CGSize measuredSize = [shadowView sizeThatFitsMinimumSize:minimumSize maximumSize:maximumSize]; - - return (YGSize){RCTYogaFloatFromCoreGraphicsFloat(measuredSize.width), - RCTYogaFloatFromCoreGraphicsFloat(measuredSize.height)}; -} - -static float RCTTextInputShadowViewBaseline(YGNodeConstRef node, const float width, const float height) -{ - RCTBaseTextInputShadowView *shadowTextView = (__bridge RCTBaseTextInputShadowView *)YGNodeGetContext(node); - - CGSize size = (CGSize){RCTCoreGraphicsFloatFromYogaFloat(width), RCTCoreGraphicsFloatFromYogaFloat(height)}; - - CGFloat lastBaseline = [shadowTextView lastBaselineForSize:size]; - - return RCTYogaFloatFromCoreGraphicsFloat(lastBaseline); -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h index 228d9ca546f..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h @@ -6,63 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import "RCTBackedTextInputDelegate.h" -#import "RCTBackedTextInputViewProtocol.h" - -@class RCTBridge; -@class RCTTextAttributes; -@class RCTTextSelection; - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTBaseTextInputView : RCTView - -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE; -- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; - -@property (nonatomic, readonly) UIView *backedTextInputView; - -@property (nonatomic, strong, nullable) RCTTextAttributes *textAttributes; -@property (nonatomic, assign) UIEdgeInsets reactPaddingInsets; -@property (nonatomic, assign) UIEdgeInsets reactBorderInsets; - -@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange; -@property (nonatomic, copy, nullable) RCTDirectEventBlock onSelectionChange; -@property (nonatomic, copy, nullable) RCTDirectEventBlock onChange; -@property (nonatomic, copy, nullable) RCTDirectEventBlock onChangeSync; -@property (nonatomic, copy, nullable) RCTDirectEventBlock onScroll; - -@property (nonatomic, assign) NSInteger mostRecentEventCount; -@property (nonatomic, assign, readonly) NSInteger nativeEventCount; -@property (nonatomic, assign) BOOL autoFocus; -@property (nonatomic, copy) NSString *submitBehavior; -@property (nonatomic, assign) BOOL selectTextOnFocus; -@property (nonatomic, assign) BOOL clearTextOnFocus; -@property (nonatomic, assign) BOOL secureTextEntry; -@property (nonatomic, copy) RCTTextSelection *selection; -@property (nonatomic, strong, nullable) NSNumber *maxLength; -@property (nonatomic, copy, nullable) NSAttributedString *attributedText; -@property (nonatomic, copy) NSString *inputAccessoryViewID; -@property (nonatomic, strong) NSString *inputAccessoryViewButtonLabel; -@property (nonatomic, assign) UIKeyboardType keyboardType; -@property (nonatomic, assign) BOOL showSoftInputOnFocus; - -/** - Sets selection intext input if both start and end are within range of the text input. - **/ -- (void)setSelectionStart:(NSInteger)start selectionEnd:(NSInteger)end; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm index 9fa7b9284df..b3b6132e39d 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm @@ -6,844 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import -#import - -/** Native iOS text field bottom keyboard offset amount */ -static const CGFloat kSingleLineKeyboardBottomOffset = 15.0; -static NSSet *returnKeyTypesSet; - -@implementation RCTBaseTextInputView { - __weak RCTBridge *_bridge; - __weak id _eventDispatcher; - BOOL _hasInputAccessoryView; - NSString *_Nullable _predictedText; - BOOL _didMoveToWindow; - NSArray *_initialValueLeadingBarButtonGroups; - NSArray *_initialValueTrailingBarButtonGroups; -} - -- (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView -{ - if (![self isDescendantOfView:scrollView]) { - // View is outside scroll view - scrollView.firstResponderViewOutsideScrollView = self.backedTextInputView; - return; - } - - UITextRange *selectedTextRange = self.backedTextInputView.selectedTextRange; - UITextSelectionRect *selection = [self.backedTextInputView selectionRectsForRange:selectedTextRange].firstObject; - CGRect focusRect; - if (selection == nil) { - // No active selection or caret - fallback to entire input frame - focusRect = self.bounds; - } else { - // Focus on text selection frame - focusRect = selection.rect; - BOOL isMultiline = [self.backedTextInputView isKindOfClass:[UITextView class]]; - if (!isMultiline) { - focusRect.size.height += kSingleLineKeyboardBottomOffset; - } - } - scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil]; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - RCTAssertParam(bridge); - - if (self = [super initWithFrame:CGRectZero]) { - _bridge = bridge; - _eventDispatcher = bridge.eventDispatcher; - [self initializeReturnKeyType]; - _initialValueLeadingBarButtonGroups = nil; - _initialValueTrailingBarButtonGroups = nil; - } - - return self; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)init) -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)decoder) -RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) - -- (UIView *)backedTextInputView -{ - RCTAssert(NO, @"-[RCTBaseTextInputView backedTextInputView] must be implemented in subclass."); - return nil; -} - -#pragma mark - RCTComponent - -- (void)didUpdateReactSubviews -{ - // Do nothing. -} - -#pragma mark - Properties - -- (void)setTextAttributes:(RCTTextAttributes *)textAttributes -{ - _textAttributes = textAttributes; - [self enforceTextAttributesIfNeeded]; -} - -- (void)enforceTextAttributesIfNeeded -{ - id backedTextInputView = self.backedTextInputView; - - NSDictionary *textAttributes = [[_textAttributes effectiveTextAttributes] mutableCopy]; - if ([textAttributes valueForKey:NSForegroundColorAttributeName] == nil) { - [textAttributes setValue:[UIColor blackColor] forKey:NSForegroundColorAttributeName]; - } - - backedTextInputView.defaultTextAttributes = textAttributes; -} - -- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets -{ - _reactPaddingInsets = reactPaddingInsets; - // We apply `paddingInsets` as `backedTextInputView`'s `textContainerInset`. - self.backedTextInputView.textContainerInset = reactPaddingInsets; - [self setNeedsLayout]; -} - -- (void)setReactBorderInsets:(UIEdgeInsets)reactBorderInsets -{ - _reactBorderInsets = reactBorderInsets; - // We apply `borderInsets` as `backedTextInputView` layout offset. - self.backedTextInputView.frame = UIEdgeInsetsInsetRect(self.bounds, reactBorderInsets); - [self setNeedsLayout]; -} - -- (NSAttributedString *)attributedText -{ - return self.backedTextInputView.attributedText; -} - -- (BOOL)textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText -{ - // When the dictation is running we can't update the attributed text on the backed up text view - // because setting the attributed string will kill the dictation. This means that we can't impose - // the settings on a dictation. - // Similarly, when the user is in the middle of inputting some text in Japanese/Chinese, there will be styling on the - // text that we should disregard. See - // https://developer.apple.com/documentation/uikit/uitextinput/1614489-markedtextrange?language=objc for more info. - // Also, updating the attributed text while inputting Korean language will break input mechanism. - // If the user added an emoji, the system adds a font attribute for the emoji and stores the original font in - // NSOriginalFont. Lastly, when entering a password, etc., there will be additional styling on the field as the native - // text view handles showing the last character for a split second. - __block BOOL fontHasBeenUpdatedBySystem = false; - [oldText enumerateAttribute:@"NSOriginalFont" - inRange:NSMakeRange(0, oldText.length) - options:0 - usingBlock:^(id value, NSRange range, BOOL *stop) { - if (value) { - fontHasBeenUpdatedBySystem = true; - } - }]; - - BOOL shouldFallbackDictation = [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"]; - if (@available(iOS 16.0, *)) { - shouldFallbackDictation = self.backedTextInputView.dictationRecognizing; - } - - BOOL shouldFallbackToBareTextComparison = shouldFallbackDictation || - [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"ko-KR"] || - self.backedTextInputView.markedTextRange || self.backedTextInputView.isSecureTextEntry || - fontHasBeenUpdatedBySystem; - - if (shouldFallbackToBareTextComparison) { - return ([newText.string isEqualToString:oldText.string]); - } else { - return ([newText isEqualToAttributedString:oldText]); - } -} - -- (void)setAttributedText:(NSAttributedString *)attributedText -{ - NSInteger eventLag = _nativeEventCount - _mostRecentEventCount; - BOOL textNeedsUpdate = NO; - // Remove tag attribute to ensure correct attributed string comparison. - NSMutableAttributedString *const backedTextInputViewTextCopy = [self.backedTextInputView.attributedText mutableCopy]; - NSMutableAttributedString *const attributedTextCopy = [attributedText mutableCopy]; - - [backedTextInputViewTextCopy removeAttribute:RCTTextAttributesTagAttributeName - range:NSMakeRange(0, backedTextInputViewTextCopy.length)]; - - [attributedTextCopy removeAttribute:RCTTextAttributesTagAttributeName - range:NSMakeRange(0, attributedTextCopy.length)]; - - textNeedsUpdate = ([self textOf:attributedTextCopy equals:backedTextInputViewTextCopy] == NO); - - if (eventLag == 0 && textNeedsUpdate) { - UITextRange *selection = self.backedTextInputView.selectedTextRange; - NSInteger oldTextLength = self.backedTextInputView.attributedText.string.length; - - self.backedTextInputView.attributedText = attributedText; - - if (selection.empty) { - // Maintaining a cursor position relative to the end of the old text. - NSInteger offsetStart = [self.backedTextInputView offsetFromPosition:self.backedTextInputView.beginningOfDocument - toPosition:selection.start]; - NSInteger offsetFromEnd = oldTextLength - offsetStart; - NSInteger newOffset = attributedText.string.length - offsetFromEnd; - UITextPosition *position = - [self.backedTextInputView positionFromPosition:self.backedTextInputView.beginningOfDocument offset:newOffset]; - [self.backedTextInputView setSelectedTextRange:[self.backedTextInputView textRangeFromPosition:position - toPosition:position] - notifyDelegate:YES]; - } - - [self updateLocalData]; - } else if (eventLag > RCTTextUpdateLagWarningThreshold) { - RCTLog( - @"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", - self.backedTextInputView.attributedText.string, - (long long)eventLag); - } -} - -- (RCTTextSelection *)selection -{ - id backedTextInputView = self.backedTextInputView; - UITextRange *selectedTextRange = backedTextInputView.selectedTextRange; - return [[RCTTextSelection new] - initWithStart:[backedTextInputView offsetFromPosition:backedTextInputView.beginningOfDocument - toPosition:selectedTextRange.start] - end:[backedTextInputView offsetFromPosition:backedTextInputView.beginningOfDocument - toPosition:selectedTextRange.end]]; -} - -- (void)setSelection:(RCTTextSelection *)selection -{ - if (!selection) { - return; - } - - id backedTextInputView = self.backedTextInputView; - - UITextRange *previousSelectedTextRange = backedTextInputView.selectedTextRange; - UITextPosition *start = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument - offset:selection.start]; - UITextPosition *end = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument - offset:selection.end]; - UITextRange *selectedTextRange = [backedTextInputView textRangeFromPosition:start toPosition:end]; - - NSInteger eventLag = _nativeEventCount - _mostRecentEventCount; - if (eventLag == 0 && ![previousSelectedTextRange isEqual:selectedTextRange]) { - [backedTextInputView setSelectedTextRange:selectedTextRange notifyDelegate:NO]; - } else if (eventLag > RCTTextUpdateLagWarningThreshold) { - RCTLog( - @"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", - backedTextInputView.attributedText.string, - (long long)eventLag); - } -} - -- (void)setSelectionStart:(NSInteger)start selectionEnd:(NSInteger)end -{ - UITextPosition *startPosition = - [self.backedTextInputView positionFromPosition:self.backedTextInputView.beginningOfDocument offset:start]; - UITextPosition *endPosition = - [self.backedTextInputView positionFromPosition:self.backedTextInputView.beginningOfDocument offset:end]; - if (startPosition && endPosition) { - UITextRange *range = [self.backedTextInputView textRangeFromPosition:startPosition toPosition:endPosition]; - [self.backedTextInputView setSelectedTextRange:range notifyDelegate:NO]; - } -} - -- (void)setTextContentType:(NSString *)type -{ - static dispatch_once_t onceToken; - static NSDictionary *contentTypeMap; - - dispatch_once(&onceToken, ^{ - NSMutableDictionary *mutableContentTypeMap = [NSMutableDictionary new]; - [mutableContentTypeMap addEntriesFromDictionary:@{ - @"none" : @"", - @"URL" : UITextContentTypeURL, - @"addressCity" : UITextContentTypeAddressCity, - @"addressCityAndState" : UITextContentTypeAddressCityAndState, - @"addressState" : UITextContentTypeAddressState, - @"countryName" : UITextContentTypeCountryName, - @"creditCardNumber" : UITextContentTypeCreditCardNumber, - @"emailAddress" : UITextContentTypeEmailAddress, - @"familyName" : UITextContentTypeFamilyName, - @"fullStreetAddress" : UITextContentTypeFullStreetAddress, - @"givenName" : UITextContentTypeGivenName, - @"jobTitle" : UITextContentTypeJobTitle, - @"location" : UITextContentTypeLocation, - @"middleName" : UITextContentTypeMiddleName, - @"name" : UITextContentTypeName, - @"namePrefix" : UITextContentTypeNamePrefix, - @"nameSuffix" : UITextContentTypeNameSuffix, - @"nickname" : UITextContentTypeNickname, - @"organizationName" : UITextContentTypeOrganizationName, - @"postalCode" : UITextContentTypePostalCode, - @"streetAddressLine1" : UITextContentTypeStreetAddressLine1, - @"streetAddressLine2" : UITextContentTypeStreetAddressLine2, - @"sublocality" : UITextContentTypeSublocality, - @"telephoneNumber" : UITextContentTypeTelephoneNumber, - @"username" : UITextContentTypeUsername, - @"password" : UITextContentTypePassword, - @"newPassword" : UITextContentTypeNewPassword, - @"oneTimeCode" : UITextContentTypeOneTimeCode, - }]; - - if (@available(iOS 15.0, *)) { - [mutableContentTypeMap addEntriesFromDictionary:@{ - @"dateTime" : UITextContentTypeDateTime, - @"flightNumber" : UITextContentTypeFlightNumber, - @"shipmentTrackingNumber" : UITextContentTypeShipmentTrackingNumber, - }]; - } - -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 /* __IPHONE_17_0 */ - if (@available(iOS 17.0, *)) { - [mutableContentTypeMap addEntriesFromDictionary:@{ - @"creditCardExpiration" : UITextContentTypeCreditCardExpiration, - @"creditCardExpirationMonth" : UITextContentTypeCreditCardExpirationMonth, - @"creditCardExpirationYear" : UITextContentTypeCreditCardExpirationYear, - @"creditCardSecurityCode" : UITextContentTypeCreditCardSecurityCode, - @"creditCardType" : UITextContentTypeCreditCardType, - @"creditCardName" : UITextContentTypeCreditCardName, - @"creditCardGivenName" : UITextContentTypeCreditCardGivenName, - @"creditCardMiddleName" : UITextContentTypeCreditCardMiddleName, - @"creditCardFamilyName" : UITextContentTypeCreditCardFamilyName, - @"birthdate" : UITextContentTypeBirthdate, - @"birthdateDay" : UITextContentTypeBirthdateDay, - @"birthdateMonth" : UITextContentTypeBirthdateMonth, - @"birthdateYear" : UITextContentTypeBirthdateYear, - }]; - } - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400 /* __IPHONE_17_4 */ - if (@available(iOS 17.4, *)) { - [mutableContentTypeMap addEntriesFromDictionary:@{ - @"cellularEID" : UITextContentTypeCellularEID, - @"cellularIMEI" : UITextContentTypeCellularIMEI, - }]; - } -#endif -#endif - - contentTypeMap = mutableContentTypeMap; - }); - - // Setting textContentType to an empty string will disable any - // default behaviour, like the autofill bar for password inputs - self.backedTextInputView.textContentType = contentTypeMap[type] ?: type; -} - -- (void)setPasswordRules:(NSString *)descriptor -{ - self.backedTextInputView.passwordRules = [UITextInputPasswordRules passwordRulesWithDescriptor:descriptor]; -} - -- (UIKeyboardType)keyboardType -{ - return self.backedTextInputView.keyboardType; -} - -- (void)setKeyboardType:(UIKeyboardType)keyboardType -{ - UIView *textInputView = self.backedTextInputView; - if (textInputView.keyboardType != keyboardType) { - textInputView.keyboardType = keyboardType; - // Without the call to reloadInputViews, the keyboard will not change until the textview field (the first responder) - // loses and regains focus. - if (textInputView.isFirstResponder) { - [textInputView reloadInputViews]; - } - } -} - -- (void)setShowSoftInputOnFocus:(BOOL)showSoftInputOnFocus -{ - (void)_showSoftInputOnFocus; - if (showSoftInputOnFocus) { - // Resets to default keyboard. - self.backedTextInputView.inputView = nil; - - // Without the call to reloadInputViews, the keyboard will not change until the textInput field (the first - // responder) loses and regains focus. - if (self.backedTextInputView.isFirstResponder) { - [self.backedTextInputView reloadInputViews]; - } - } else { - // Hides keyboard, but keeps blinking cursor. - self.backedTextInputView.inputView = [UIView new]; - } -} - -- (NSString *)inputAccessoryViewButtonLabel -{ - return self.backedTextInputView.inputAccessoryViewButtonLabel; -} - -- (void)setInputAccessoryViewButtonLabel:(NSString *)inputAccessoryViewButtonLabel -{ - self.backedTextInputView.inputAccessoryViewButtonLabel = inputAccessoryViewButtonLabel; -} - -- (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts -{ -#if TARGET_OS_IOS - // Initialize the initial values only once - if (_initialValueLeadingBarButtonGroups == nil) { - // Capture initial values of leading and trailing button groups - _initialValueLeadingBarButtonGroups = self.backedTextInputView.inputAssistantItem.leadingBarButtonGroups; - _initialValueTrailingBarButtonGroups = self.backedTextInputView.inputAssistantItem.trailingBarButtonGroups; - } - - if (disableKeyboardShortcuts) { - self.backedTextInputView.inputAssistantItem.leadingBarButtonGroups = @[]; - self.backedTextInputView.inputAssistantItem.trailingBarButtonGroups = @[]; - } else { - // Restore the initial values - self.backedTextInputView.inputAssistantItem.leadingBarButtonGroups = _initialValueLeadingBarButtonGroups; - self.backedTextInputView.inputAssistantItem.trailingBarButtonGroups = _initialValueTrailingBarButtonGroups; - } -#endif -} - -#pragma mark - RCTBackedTextInputDelegate - -- (BOOL)textInputShouldBeginEditing -{ - return YES; -} - -- (void)textInputDidBeginEditing -{ - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus - reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] - key:nil - eventCount:_nativeEventCount]; -} - -- (BOOL)textInputShouldEndEditing -{ - return YES; -} - -- (void)textInputDidEndEditing -{ - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd - reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] - key:nil - eventCount:_nativeEventCount]; - - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur - reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] - key:nil - eventCount:_nativeEventCount]; -} - -- (BOOL)textInputShouldSubmitOnReturn -{ - const BOOL shouldSubmit = - [_submitBehavior isEqualToString:@"blurAndSubmit"] || [_submitBehavior isEqualToString:@"submit"]; - if (shouldSubmit) { - // We send `submit` event here, in `textInputShouldSubmit` - // (not in `textInputDidReturn)`, because of semantic of the event: - // `onSubmitEditing` is called when "Submit" button - // (the blue key on onscreen keyboard) did pressed - // (no connection to any specific "submitting" process). - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit - reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] - key:nil - eventCount:_nativeEventCount]; - } - return shouldSubmit; -} - -- (BOOL)textInputShouldReturn -{ - return [_submitBehavior isEqualToString:@"blurAndSubmit"]; -} - -- (void)textInputDidReturn -{ - // Does nothing. -} - -- (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range -{ - id backedTextInputView = self.backedTextInputView; - - if (!backedTextInputView.textWasPasted) { - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress - reactTag:self.reactTag - text:nil - key:text - eventCount:_nativeEventCount]; - } - - if (_maxLength) { - NSInteger allowedLength = MAX( - _maxLength.integerValue - (NSInteger)backedTextInputView.attributedText.string.length + (NSInteger)range.length, - 0); - - if (text.length > allowedLength) { - // If we typed/pasted more than one character, limit the text inputted. - if (text.length > 1) { - if (allowedLength > 0) { - // make sure unicode characters that are longer than 16 bits (such as emojis) are not cut off - NSRange cutOffCharacterRange = [text rangeOfComposedCharacterSequenceAtIndex:allowedLength - 1]; - if (cutOffCharacterRange.location + cutOffCharacterRange.length > allowedLength) { - // the character at the length limit takes more than 16bits, truncation should end at the character before - allowedLength = cutOffCharacterRange.location; - } - } - if (allowedLength <= 0) { - return nil; - } - // Truncate the input string so the result is exactly maxLength - NSString *limitedString = [text substringToIndex:allowedLength]; - NSMutableAttributedString *newAttributedText = [backedTextInputView.attributedText mutableCopy]; - // Apply text attributes if original input view doesn't have text. - if (backedTextInputView.attributedText.length == 0) { - newAttributedText = [[NSMutableAttributedString alloc] - initWithString:[self.textAttributes applyTextAttributesToText:limitedString] - attributes:self.textAttributes.effectiveTextAttributes]; - } else { - [newAttributedText replaceCharactersInRange:range withString:limitedString]; - } - backedTextInputView.attributedText = newAttributedText; - _predictedText = newAttributedText.string; - - // Collapse selection at end of insert to match normal paste behavior. - UITextPosition *insertEnd = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument - offset:(range.location + allowedLength)]; - [backedTextInputView setSelectedTextRange:[backedTextInputView textRangeFromPosition:insertEnd - toPosition:insertEnd] - notifyDelegate:YES]; - - [self textInputDidChange]; - } - - return nil; // Rejecting the change. - } - } - - if (range.location + range.length > backedTextInputView.attributedText.string.length) { - _predictedText = backedTextInputView.attributedText.string; - } else if (text != nil) { - _predictedText = [backedTextInputView.attributedText.string stringByReplacingCharactersInRange:range - withString:text]; - } - - return text; // Accepting the change. -} - -- (void)textInputDidChange -{ - [self updateLocalData]; - - id backedTextInputView = self.backedTextInputView; - - // Detect when `backedTextInputView` updates happened that didn't invoke `shouldChangeTextInRange` - // (e.g. typing simplified Chinese in pinyin will insert and remove spaces without - // calling shouldChangeTextInRange). This will cause JS to get out of sync so we - // update the mismatched range. - NSRange currentRange; - NSRange predictionRange; - if (findMismatch(backedTextInputView.attributedText.string, _predictedText, ¤tRange, &predictionRange)) { - NSString *replacement = [backedTextInputView.attributedText.string substringWithRange:currentRange]; - [self textInputShouldChangeText:replacement inRange:predictionRange]; - // JS will assume the selection changed based on the location of our shouldChangeTextInRange, so reset it. - [self textInputDidChangeSelection]; - } - - _nativeEventCount++; - - if (_onChange) { - _onChange(@{ - @"text" : [self.attributedText.string copy], - @"target" : self.reactTag, - @"eventCount" : @(_nativeEventCount), - }); - } -} - -- (void)textInputDidChangeSelection -{ - if (!_onSelectionChange) { - return; - } - - RCTTextSelection *selection = self.selection; - - _onSelectionChange(@{ - @"selection" : @{ - @"start" : @(selection.start), - @"end" : @(selection.end), - }, - }); -} - -- (void)updateLocalData -{ - [self enforceTextAttributesIfNeeded]; - - [_bridge.uiManager setLocalData:[self.backedTextInputView.attributedText copy] forView:self]; -} - -#pragma mark - Layout (in UIKit terms, with all insets) - -- (CGSize)intrinsicContentSize -{ - CGSize size = self.backedTextInputView.intrinsicContentSize; - size.width += _reactBorderInsets.left + _reactBorderInsets.right; - size.height += _reactBorderInsets.top + _reactBorderInsets.bottom; - // Returning value DOES include border and padding insets. - return size; -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - CGFloat compoundHorizontalBorderInset = _reactBorderInsets.left + _reactBorderInsets.right; - CGFloat compoundVerticalBorderInset = _reactBorderInsets.top + _reactBorderInsets.bottom; - - size.width -= compoundHorizontalBorderInset; - size.height -= compoundVerticalBorderInset; - - // Note: `paddingInsets` was already included in `backedTextInputView` size - // because it was applied as `textContainerInset`. - CGSize fittingSize = [self.backedTextInputView sizeThatFits:size]; - - fittingSize.width += compoundHorizontalBorderInset; - fittingSize.height += compoundVerticalBorderInset; - - // Returning value DOES include border and padding insets. - return fittingSize; -} - -#pragma mark - Accessibility - -- (UIView *)reactAccessibilityElement -{ - return self.backedTextInputView; -} - -#pragma mark - Focus Control - -- (void)reactFocus -{ - [self.backedTextInputView reactFocus]; - - if (_clearTextOnFocus) { - self.backedTextInputView.attributedText = [NSAttributedString new]; - } - - if (_selectTextOnFocus) { - [self.backedTextInputView selectAll:nil]; - } -} - -- (void)reactBlur -{ - [self.backedTextInputView reactBlur]; -} - -- (void)didMoveToWindow -{ - if (self.autoFocus && !_didMoveToWindow) { - [self.backedTextInputView reactFocus]; - [self initializeReturnKeyType]; - } else { - [self.backedTextInputView reactFocusIfNeeded]; - } - - _didMoveToWindow = YES; -} - -#pragma mark - Custom Input Accessory View - -- (void)didSetProps:(NSArray *)changedProps -{ - if ([changedProps containsObject:@"inputAccessoryViewID"] && self.inputAccessoryViewID) { - [self setCustomInputAccessoryViewWithNativeID:self.inputAccessoryViewID]; - } else if (!self.inputAccessoryViewID) { - [self setDefaultInputAccessoryView]; - } -} - -- (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID -{ - __weak RCTBaseTextInputView *weakSelf = self; - [_bridge.uiManager rootViewForReactTag:self.reactTag - withCompletion:^(UIView *rootView) { - RCTBaseTextInputView *strongSelf = weakSelf; - if (rootView) { - UIView *accessoryView = [strongSelf->_bridge.uiManager viewForNativeID:nativeID - withRootTag:rootView.reactTag]; - if (accessoryView && [accessoryView isKindOfClass:[RCTInputAccessoryView class]]) { - strongSelf.backedTextInputView.inputAccessoryView = - ((RCTInputAccessoryView *)accessoryView).inputAccessoryView; - [strongSelf reloadInputViewsIfNecessary]; - } - } - }]; -} - -- (NSString *)returnKeyTypeToString:(UIReturnKeyType)returnKeyType -{ - switch (returnKeyType) { - case UIReturnKeyGo: - return @"Go"; - case UIReturnKeyNext: - return @"Next"; - case UIReturnKeySearch: - return @"Search"; - case UIReturnKeySend: - return @"Send"; - case UIReturnKeyYahoo: - return @"Yahoo"; - case UIReturnKeyGoogle: - return @"Google"; - case UIReturnKeyRoute: - return @"Route"; - case UIReturnKeyJoin: - return @"Join"; - case UIReturnKeyEmergencyCall: - return @"Emergency Call"; - case UIReturnKeyDefault: - case UIReturnKeyContinue: - case UIReturnKeyDone: - default: - return @"Done"; - } -} - -- (void)initializeReturnKeyType -{ - returnKeyTypesSet = [NSSet setWithObjects:@(UIReturnKeyDone), - @(UIReturnKeyGo), - @(UIReturnKeyNext), - @(UIReturnKeySearch), - @(UIReturnKeySend), - @(UIReturnKeyYahoo), - @(UIReturnKeyGoogle), - @(UIReturnKeyRoute), - @(UIReturnKeyJoin), - @(UIReturnKeyRoute), - @(UIReturnKeyEmergencyCall), - nil]; -} - -- (void)setDefaultInputAccessoryView -{ - UIView *textInputView = self.backedTextInputView; - UIKeyboardType keyboardType = textInputView.keyboardType; - NSString *inputAccessoryViewButtonLabel = textInputView.inputAccessoryViewButtonLabel; - - // These keyboard types (all are number pads) don't have a Return Key button by default, - // so we create an `inputAccessoryView` with this button for them. - - UIReturnKeyType returnKeyType = textInputView.returnKeyType; - - BOOL containsKeyType = [returnKeyTypesSet containsObject:@(returnKeyType)]; - BOOL containsInputAccessoryViewButtonLabel = inputAccessoryViewButtonLabel != nil; - - BOOL shouldHaveInputAccessoryView = - (keyboardType == UIKeyboardTypeNumberPad || keyboardType == UIKeyboardTypePhonePad || - keyboardType == UIKeyboardTypeDecimalPad || keyboardType == UIKeyboardTypeASCIICapableNumberPad) && - (containsKeyType || containsInputAccessoryViewButtonLabel); - - if (_hasInputAccessoryView == shouldHaveInputAccessoryView) { - return; - } - - _hasInputAccessoryView = shouldHaveInputAccessoryView; - - if (shouldHaveInputAccessoryView) { - NSString *buttonLabel = inputAccessoryViewButtonLabel != nil ? inputAccessoryViewButtonLabel - : [self returnKeyTypeToString:returnKeyType]; - - UIToolbar *toolbarView = [UIToolbar new]; - [toolbarView sizeToFit]; - UIBarButtonItem *flexibleSpace = - [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; - UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:buttonLabel - style:UIBarButtonItemStylePlain - target:self - action:@selector(handleInputAccessoryDoneButton)]; - toolbarView.items = @[ flexibleSpace, doneButton ]; - textInputView.inputAccessoryView = toolbarView; - } else { - textInputView.inputAccessoryView = nil; - } - [self reloadInputViewsIfNecessary]; -} - -- (void)reloadInputViewsIfNecessary -{ - // We have to call `reloadInputViews` for focused text inputs to update an accessory view. - if (self.backedTextInputView.isFirstResponder) { - [self.backedTextInputView reloadInputViews]; - } -} - -- (void)handleInputAccessoryDoneButton -{ - // Ignore the value of whether we submitted; just make sure the submit event is called if necessary. - [self textInputShouldSubmitOnReturn]; - if ([self textInputShouldReturn]) { - [self.backedTextInputView endEditing:YES]; - } -} - -#pragma mark - Helpers - -static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, NSRange *secondRange) -{ - NSInteger firstMismatch = -1; - for (NSUInteger ii = 0; ii < MAX(first.length, second.length); ii++) { - if (ii >= first.length || ii >= second.length || [first characterAtIndex:ii] != [second characterAtIndex:ii]) { - firstMismatch = ii; - break; - } - } - - if (firstMismatch == -1) { - return NO; - } - - NSUInteger ii = second.length; - NSUInteger lastMismatch = first.length; - while (ii > firstMismatch && lastMismatch > firstMismatch) { - if ([first characterAtIndex:(lastMismatch - 1)] != [second characterAtIndex:(ii - 1)]) { - break; - } - ii--; - lastMismatch--; - } - - *firstRange = NSMakeRange(firstMismatch, lastMismatch - firstMismatch); - *secondRange = NSMakeRange(firstMismatch, ii - firstMismatch); - return YES; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h index 2c7a815c313..e88bbcf9a24 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h @@ -6,12 +6,3 @@ */ #import "RCTBaseTextViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTBaseTextInputViewManager : RCTBaseTextViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm index 3503ccf4522..1c3166d6146 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm @@ -6,176 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import - -@interface RCTBaseTextInputViewManager () - -@end - -@implementation RCTBaseTextInputViewManager { - NSHashTable *_shadowViews; -} - -RCT_EXPORT_MODULE() - -#pragma mark - Unified properties - -RCT_REMAP_VIEW_PROPERTY(autoCapitalize, backedTextInputView.autocapitalizationType, UITextAutocapitalizationType) -RCT_REMAP_VIEW_PROPERTY(autoCorrect, backedTextInputView.autocorrectionType, UITextAutocorrectionType) -RCT_REMAP_VIEW_PROPERTY(contextMenuHidden, backedTextInputView.contextMenuHidden, BOOL) -RCT_REMAP_VIEW_PROPERTY(editable, backedTextInputView.editable, BOOL) -RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, backedTextInputView.enablesReturnKeyAutomatically, BOOL) -RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, backedTextInputView.keyboardAppearance, UIKeyboardAppearance) -RCT_REMAP_VIEW_PROPERTY(placeholder, backedTextInputView.placeholder, NSString) -RCT_REMAP_VIEW_PROPERTY(placeholderTextColor, backedTextInputView.placeholderColor, UIColor) -RCT_REMAP_VIEW_PROPERTY(returnKeyType, backedTextInputView.returnKeyType, UIReturnKeyType) -RCT_REMAP_VIEW_PROPERTY(selectionColor, backedTextInputView.tintColor, UIColor) -RCT_REMAP_VIEW_PROPERTY(spellCheck, backedTextInputView.spellCheckingType, UITextSpellCheckingType) -RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL) -RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode) -RCT_REMAP_VIEW_PROPERTY(scrollEnabled, backedTextInputView.scrollEnabled, BOOL) -RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL) -RCT_REMAP_VIEW_PROPERTY(smartInsertDelete, backedTextInputView.smartInsertDeleteType, UITextSmartInsertDeleteType) - -RCT_EXPORT_VIEW_PROPERTY(autoFocus, BOOL) -RCT_EXPORT_VIEW_PROPERTY(submitBehavior, NSString) -RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL) -RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType) -RCT_EXPORT_VIEW_PROPERTY(showSoftInputOnFocus, BOOL) -RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber) -RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL) -RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection) -RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewID, NSString) -RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewButtonLabel, NSString) -RCT_EXPORT_VIEW_PROPERTY(textContentType, NSString) -RCT_EXPORT_VIEW_PROPERTY(passwordRules, NSString) -RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger) -RCT_EXPORT_VIEW_PROPERTY(disableKeyboardShortcuts, BOOL) -RCT_EXPORT_VIEW_PROPERTY(acceptDragAndDropTypes, NSArray) - -RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onKeyPressSync, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) - -RCT_EXPORT_SHADOW_PROPERTY(text, NSString) -RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString) -RCT_EXPORT_SHADOW_PROPERTY(onContentSizeChange, RCTDirectEventBlock) - -RCT_CUSTOM_VIEW_PROPERTY(multiline, BOOL, UIView) -{ - // No op. - // This View Manager doesn't use this prop but it must be exposed here via ViewConfig to enable Fabric component use - // it. -} - -- (RCTShadowView *)shadowView -{ - RCTBaseTextInputShadowView *shadowView = [[RCTBaseTextInputShadowView alloc] initWithBridge:self.bridge]; - shadowView.textAttributes.fontSizeMultiplier = - [[[self.bridge moduleForName:@"AccessibilityManager" - lazilyLoadIfNecessary:YES] valueForKey:@"multiplier"] floatValue]; - [_shadowViews addObject:shadowView]; - return shadowView; -} - -- (void)setBridge:(RCTBridge *)bridge -{ - [super setBridge:bridge]; - - _shadowViews = [NSHashTable weakObjectsHashTable]; - - [bridge.uiManager.observerCoordinator addObserver:self]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleDidUpdateMultiplierNotification) - name:@"RCTAccessibilityManagerDidUpdateMultiplierNotification" - object:[bridge moduleForName:@"AccessibilityManager" - lazilyLoadIfNecessary:YES]]; -} - -RCT_EXPORT_METHOD(focus : (nonnull NSNumber *)viewTag) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[viewTag]; - [view reactFocus]; - }]; -} - -RCT_EXPORT_METHOD(blur : (nonnull NSNumber *)viewTag) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[viewTag]; - [view reactBlur]; - }]; -} - -RCT_EXPORT_METHOD( - setTextAndSelection : (nonnull NSNumber *)viewTag mostRecentEventCount : (NSInteger) - mostRecentEventCount value : (NSString *)value start : (NSInteger)start end : (NSInteger)end) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTBaseTextInputView *view = (RCTBaseTextInputView *)viewRegistry[viewTag]; - NSInteger eventLag = view.nativeEventCount - mostRecentEventCount; - if (eventLag != 0) { - return; - } - RCTExecuteOnUIManagerQueue(^{ - RCTBaseTextInputShadowView *shadowView = - (RCTBaseTextInputShadowView *)[self.bridge.uiManager shadowViewForReactTag:viewTag]; - if (value != nullptr) { - [shadowView setText:value]; - } - [self.bridge.uiManager setNeedsLayout]; - RCTExecuteOnMainQueue(^{ - [view setSelectionStart:start selectionEnd:end]; - }); - }); - }]; -} - -#pragma mark - RCTUIManagerObserver - -- (void)uiManagerWillPerformMounting:(__unused RCTUIManager *)uiManager -{ - for (RCTBaseTextInputShadowView *shadowView in _shadowViews) { - [shadowView uiManagerWillPerformMounting]; - } -} - -#pragma mark - Font Size Multiplier - -- (void)handleDidUpdateMultiplierNotification -{ - CGFloat fontSizeMultiplier = - [[[self.bridge moduleForName:@"AccessibilityManager"] valueForKey:@"multiplier"] floatValue]; - - NSHashTable *shadowViews = _shadowViews; - RCTExecuteOnUIManagerQueue(^{ - for (RCTBaseTextInputShadowView *shadowView in shadowViews) { - shadowView.textAttributes.fontSizeMultiplier = fontSizeMultiplier; - [shadowView dirtyLayout]; - } - - [self.bridge.uiManager setNeedsLayout]; - }); -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.h b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.h index e56ad959390..103dec8dfad 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.h @@ -6,12 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTInputAccessoryShadowView : RCTShadowView - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.mm b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.mm index a9e6472a3c0..02a3e38fbc5 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.mm @@ -6,19 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -@implementation RCTInputAccessoryShadowView - -- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:subview atIndex:atIndex]; - subview.width = (YGValue){static_cast(RCTScreenSize().width), YGUnitPoint}; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.h b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.h index f209cb578a9..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.h @@ -6,17 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@class RCTBridge; -@class RCTInputAccessoryViewContent; - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTInputAccessoryView : UIView - -- (instancetype)initWithBridge:(RCTBridge *)bridge; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.mm b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.mm index 347e5de0c8c..11fbdcba649 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.mm @@ -6,77 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -#import - -@interface RCTInputAccessoryView () - -// Overriding `inputAccessoryView` to `readwrite`. -@property (nonatomic, readwrite, retain) UIView *inputAccessoryView; - -@end - -@implementation RCTInputAccessoryView { - BOOL _shouldBecomeFirstResponder; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super init]) { - _inputAccessoryView = [RCTInputAccessoryViewContent new]; - RCTTouchHandler *const touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge]; - [touchHandler attachToView:_inputAccessoryView]; - } - return self; -} - -- (BOOL)canBecomeFirstResponder -{ - return true; -} - -- (void)reactSetFrame:(CGRect)frame -{ - [_inputAccessoryView reactSetFrame:frame]; - - if (_shouldBecomeFirstResponder) { - _shouldBecomeFirstResponder = NO; - [self becomeFirstResponder]; - } -} - -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index -{ - [super insertReactSubview:subview atIndex:index]; - [_inputAccessoryView insertReactSubview:subview atIndex:index]; -} - -- (void)removeReactSubview:(UIView *)subview -{ - [super removeReactSubview:subview]; - [_inputAccessoryView removeReactSubview:subview]; -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as subviews are managed by `insertReactSubview:atIndex:`. -} - -- (void)didSetProps:(NSArray *)changedProps -{ - // If the accessory view is not linked to a text input via nativeID, assume it is - // a standalone component that should get focus whenever it is rendered. - if (![changedProps containsObject:@"nativeID"] && !self.nativeID) { - _shouldBecomeFirstResponder = YES; - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.h b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.h index cec4f010529..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.h @@ -6,12 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTInputAccessoryViewContent : UIView - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.mm b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.mm index 7c617fe2fa8..3ea241d75c4 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.mm @@ -6,73 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -@implementation RCTInputAccessoryViewContent { - UIView *_safeAreaContainer; - NSLayoutConstraint *_heightConstraint; -} - -- (instancetype)init -{ - if (self = [super init]) { - _safeAreaContainer = [UIView new]; - [self addSubview:_safeAreaContainer]; - - // Use autolayout to position the view properly and take into account - // safe area insets on iPhone X. - // TODO: Support rotation, anchor to left and right without breaking frame x coordinate (T27974328). - self.autoresizingMask = UIViewAutoresizingFlexibleHeight; - _safeAreaContainer.translatesAutoresizingMaskIntoConstraints = NO; - - _heightConstraint = [_safeAreaContainer.heightAnchor constraintEqualToConstant:0]; - _heightConstraint.active = YES; - - [_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES; - [_safeAreaContainer.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES; - [_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = YES; - [_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = YES; - } - return self; -} - -- (CGSize)intrinsicContentSize -{ - // This is needed so the view size is based on autolayout constraints. - return CGSizeZero; -} - -- (void)reactSetFrame:(CGRect)frame -{ - // We still need to set the frame here, otherwise it won't be - // measured until moved to the window during the keyboard opening - // animation. If this happens, the height will be animated from 0 to - // its actual size and we don't want that. - [self setFrame:frame]; - [_safeAreaContainer setFrame:frame]; - - _heightConstraint.constant = frame.size.height; - [self layoutIfNeeded]; -} - -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index -{ - [super insertReactSubview:subview atIndex:index]; - [_safeAreaContainer insertSubview:subview atIndex:index]; -} - -- (void)removeReactSubview:(UIView *)subview -{ - [super removeReactSubview:subview]; - [subview removeFromSuperview]; - if ([[_safeAreaContainer subviews] count] == 0 && [self isFirstResponder]) { - [self resignFirstResponder]; - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.h b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.h index 9cafbbb13ea..b6bc9e2e3c2 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.h @@ -6,12 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTInputAccessoryViewManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.mm b/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.mm deleted file mode 100644 index 34e4a575285..00000000000 --- a/packages/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.mm +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -@implementation RCTInputAccessoryViewManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - return [[RCTInputAccessoryView alloc] initWithBridge:self.bridge]; -} - -- (RCTShadowView *)shadowView -{ - return [RCTInputAccessoryShadowView new]; -} - -RCT_REMAP_VIEW_PROPERTY(backgroundColor, inputAccessoryView.backgroundColor, UIColor) - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h deleted file mode 100644 index 08ede6a0a15..00000000000 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import "RCTBaseTextInputView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSinglelineTextInputView : RCTBaseTextInputView - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.mm deleted file mode 100644 index 9598167b656..00000000000 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.mm +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import - -@implementation RCTSinglelineTextInputView { - RCTUITextField *_backedTextInputView; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super initWithBridge:bridge]) { - // `submitBehavior` defaults to `"blurAndSubmit"` for by design. - self.submitBehavior = @"blurAndSubmit"; - - _backedTextInputView = [[RCTUITextField alloc] initWithFrame:self.bounds]; - _backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _backedTextInputView.textInputDelegate = self; - - [self addSubview:_backedTextInputView]; - } - - return self; -} - -- (id)backedTextInputView -{ - return _backedTextInputView; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h deleted file mode 100644 index dd2bce846a8..00000000000 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import "RCTBaseTextInputViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSinglelineTextInputViewManager : RCTBaseTextInputViewManager - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm deleted file mode 100644 index 13cd754c765..00000000000 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -@implementation RCTSinglelineTextInputViewManager - -RCT_EXPORT_MODULE() - -- (RCTShadowView *)shadowView -{ - RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; - - shadowView.maximumNumberOfLines = 1; - - return shadowView; -} - -- (UIView *)view -{ - return [[RCTSinglelineTextInputView alloc] initWithBridge:self.bridge]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.h b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.h deleted file mode 100644 index 5182a2f1883..00000000000 --- a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import "RCTBaseTextShadowView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTVirtualTextShadowView : RCTBaseTextShadowView - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.mm b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.mm deleted file mode 100644 index 682d683dccb..00000000000 --- a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.mm +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -#import - -@implementation RCTVirtualTextShadowView { - BOOL _isLayoutDirty; -} - -#pragma mark - Layout - -- (void)dirtyLayout -{ - [super dirtyLayout]; - - if (_isLayoutDirty) { - return; - } - _isLayoutDirty = YES; - - [self.superview dirtyLayout]; -} - -- (void)clearLayout -{ - _isLayoutDirty = NO; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.h b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.h index d69a7747fda..41a8e64d9a5 100644 --- a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.h +++ b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.h @@ -6,23 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTVirtualTextView : UIView - -/** - * (Experimental and unused for Paper) Pointer event handlers. - */ -@property (nonatomic, assign) RCTBubblingEventBlock onClick; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.mm b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.mm index 8ce4101ad12..ae0fa33d7cc 100644 --- a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.mm +++ b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextView.mm @@ -6,11 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTVirtualTextView - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.h b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.h index 94d5cef34cb..e88bbcf9a24 100644 --- a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.h +++ b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.h @@ -6,12 +6,3 @@ */ #import "RCTBaseTextViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTVirtualTextViewManager : RCTBaseTextViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.mm b/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.mm deleted file mode 100644 index e3d054ed17f..00000000000 --- a/packages/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.mm +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import -#import -#import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTVirtualTextViewManager - -RCT_EXPORT_MODULE(RCTVirtualText) - -- (UIView *)view -{ - return [RCTVirtualTextView new]; -} - -- (RCTShadowView *)shadowView -{ - return [RCTVirtualTextShadowView new]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.h b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.h index 98d3657d34c..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.h +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.h @@ -6,16 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperExampleView : UIView - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.m b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.m index 7451c4b2762..8ee12951b27 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.m +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleView.m @@ -6,52 +6,3 @@ */ #import "RCTWrapperExampleView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -@implementation RCTWrapperExampleView { - NSTimer *_timer; - CGSize _intrinsicContentSize; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - self.backgroundColor = [UIColor whiteColor]; - - _intrinsicContentSize = CGSizeMake(64, 64); - _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES]; - - UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(tick)]; - [self addGestureRecognizer:gestureRecognizer]; - } - return self; -} - -- (void)tick -{ - _intrinsicContentSize.width = 32 + arc4random() % 128; - _intrinsicContentSize.height = 32 + arc4random() % 128; - - [self invalidateIntrinsicContentSize]; - [self.superview setNeedsLayout]; -} - -- (CGSize)intrinsicContentSize -{ - return _intrinsicContentSize; -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - return CGSizeMake(MIN(size.width, _intrinsicContentSize.width), MIN(size.height, _intrinsicContentSize.height)); -} - -@end - -RCT_WRAPPER_FOR_VIEW(RCTWrapperExampleView) - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.h b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.h index 66d97e91fb8..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.h +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.h @@ -6,16 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperExampleViewController : UIViewController - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.m b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.m index 48644c88ccd..60254cf968e 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.m +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperExampleViewController.m @@ -6,22 +6,3 @@ */ #import "RCTWrapperExampleViewController.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import "RCTWrapperExampleView.h" - -@implementation RCTWrapperExampleViewController - -- (void)loadView -{ - self.view = [RCTWrapperExampleView new]; -} - -@end - -RCT_WRAPPER_FOR_VIEW_CONTROLLER(RCTWrapperExampleViewController) - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.h b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.h index 6e0d747b667..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.h +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.h @@ -6,20 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@class RCTBridge; - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperReactRootViewController : UIViewController - -- (instancetype)initWithBridge:(RCTBridge *)bridge; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.m b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.m index 0928e30552a..63e102b6a18 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.m +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewController.m @@ -6,45 +6,3 @@ */ #import "RCTWrapperReactRootViewController.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -#import "RCTWrapperExampleView.h" - -@implementation RCTWrapperReactRootViewController { - RCTBridge *_bridge; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super initWithNibName:nil bundle:nil]) { - _bridge = bridge; - } - - return self; -} - -- (void)loadView -{ - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge - moduleName:@"WrapperExample" - initialProperties:@{}]; - - rootView.backgroundColor = [UIColor whiteColor]; - - UIActivityIndicatorView *progressIndicatorView = - [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; - [progressIndicatorView startAnimating]; - rootView.loadingView = progressIndicatorView; - - rootView.sizeFlexibility = RCTRootViewSizeFlexibilityWidthAndHeight; - self.view = rootView; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.h b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.h index f94bb30cc78..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.h +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.h @@ -6,18 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperReactRootViewManager : RCTWrapperViewManager - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.m b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.m index b5b2d4c601d..c78e446ba76 100644 --- a/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.m +++ b/packages/react-native/Libraries/Wrapper/Example/RCTWrapperReactRootViewManager.m @@ -6,30 +6,3 @@ */ #import "RCTWrapperReactRootViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -#import "RCTWrapperReactRootViewController.h" - -@implementation RCTWrapperReactRootViewManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - RCTWrapperViewControllerHostingView *contentViewControllerHostingView = [RCTWrapperViewControllerHostingView new]; - - contentViewControllerHostingView.contentViewController = - [[RCTWrapperReactRootViewController alloc] initWithBridge:self.bridge]; - - RCTWrapperView *wrapperView = [super view]; - wrapperView.contentView = contentViewControllerHostingView; - return wrapperView; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapper.h b/packages/react-native/Libraries/Wrapper/RCTWrapper.h index 5b5fbead5d7..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapper.h +++ b/packages/react-native/Libraries/Wrapper/RCTWrapper.h @@ -6,68 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -// Umbrella header with macros - -// RCT_WRAPPER_FOR_VIEW -#define RCT_WRAPPER_FOR_VIEW(ClassName) \ - NS_ASSUME_NONNULL_BEGIN \ - \ - __attribute__((deprecated("This API will be removed along with the legacy architecture."))) \ - @interface ClassName \ - ##Manager : RCTWrapperViewManager \ - \ - @end \ - \ - NS_ASSUME_NONNULL_END \ - \ - @implementation ClassName \ - ##Manager \ - \ - RCT_EXPORT_MODULE() \ - \ - - (UIView *)view \ - { \ - RCTWrapperView *wrapperView = [super view]; \ - wrapperView.contentView = [ClassName new]; \ - return wrapperView; \ - } \ - \ - @end - -// RCT_WRAPPER_FOR_VIEW_CONTROLLER -#define RCT_WRAPPER_FOR_VIEW_CONTROLLER(ClassName) \ - \ - NS_ASSUME_NONNULL_BEGIN \ - \ - __attribute__((deprecated("This API will be removed along with the legacy architecture."))) \ - @interface ClassName \ - ##Manager : RCTWrapperViewManager \ - \ - @end \ - \ - NS_ASSUME_NONNULL_END \ - \ - @implementation ClassName \ - ##Manager \ - \ - RCT_EXPORT_MODULE() \ - \ - - (UIView *)view \ - { \ - RCTWrapperViewControllerHostingView *contentViewControllerHostingView = [RCTWrapperViewControllerHostingView new]; \ - contentViewControllerHostingView.contentViewController = [[ClassName alloc] initWithNibName:nil bundle:nil]; \ - RCTWrapperView *wrapperView = [super view]; \ - wrapperView.contentView = contentViewControllerHostingView; \ - return wrapperView; \ - } \ - \ - @end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.h b/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.h index c2cf5bc8aaa..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.h +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.h @@ -6,22 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -@class RCTBridge; - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperShadowView : RCTShadowView - -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.m b/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.m index dfbebc9cd6b..d7e74636f24 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.m +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperShadowView.m @@ -6,133 +6,3 @@ */ #import "RCTWrapperShadowView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -#import "RCTWrapperView.h" - -@implementation RCTWrapperShadowView { - __weak RCTBridge *_bridge; - RCTWrapperMeasureBlock _measureBlock; - CGSize _intrinsicContentSize; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super init]) { - _bridge = bridge; - YGNodeSetMeasureFunc(self.yogaNode, RCTWrapperShadowViewMeasure); - } - - return self; -} - -static YGSize RCTWrapperShadowViewMeasure( - YGNodeConstRef node, - float width, - YGMeasureMode widthMode, - float height, - YGMeasureMode heightMode) -{ - CGSize minimumSize = CGSizeMake(0, 0); - CGSize maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); - - switch (widthMode) { - case YGMeasureModeUndefined: - break; - case YGMeasureModeExactly: - minimumSize.width = width; - maximumSize.width = width; - break; - case YGMeasureModeAtMost: - maximumSize.width = width; - break; - } - - switch (heightMode) { - case YGMeasureModeUndefined: - break; - case YGMeasureModeExactly: - minimumSize.height = height; - maximumSize.height = height; - break; - case YGMeasureModeAtMost: - maximumSize.height = height; - break; - } - - RCTWrapperShadowView *shadowView = (__bridge RCTWrapperShadowView *)YGNodeGetContext(node); - CGSize size = [shadowView measureWithMinimumSize:minimumSize maximumSize:maximumSize]; - - return (YGSize){RCTYogaFloatFromCoreGraphicsFloat(size.width), RCTYogaFloatFromCoreGraphicsFloat(size.height)}; -} - -- (CGSize)measureWithMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize -{ - dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC); - - if (!_measureBlock) { - RCTBridge *bridge = _bridge; - __block RCTWrapperMeasureBlock measureBlock; - NSNumber *reactTag = self.reactTag; - - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - dispatch_async(dispatch_get_main_queue(), ^{ - RCTUIManager *uiManager = bridge.uiManager; - RCTWrapperView *view = (RCTWrapperView *)[uiManager viewForReactTag:reactTag]; - measureBlock = view.measureBlock; - - dispatch_semaphore_signal(semaphore); - }); - - if (dispatch_semaphore_wait(semaphore, timeout)) { - RCTLogError(@"Unable to retrieve `measureBlock` for view (%@) because the main thread is busy.", self); - } - - _measureBlock = measureBlock; - } - - if (!_measureBlock) { - return maximumSize; - } - - __block CGSize size = maximumSize; - - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - dispatch_async(dispatch_get_main_queue(), ^{ - size = self->_measureBlock(minimumSize, maximumSize); - dispatch_semaphore_signal(semaphore); - }); - - if (dispatch_semaphore_wait(semaphore, timeout)) { - RCTLogError(@"Unable to compute layout for view (%@) because the main thread is busy.", self); - } - - return size; -} - -- (BOOL)isYogaLeafNode -{ - return YES; -} - -- (CGSize)intrinsicContentSize -{ - return _intrinsicContentSize; -} - -- (void)setIntrinsicContentSize:(CGSize)size -{ - _intrinsicContentSize = size; - YGNodeMarkDirty(self.yogaNode); -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperView.h b/packages/react-native/Libraries/Wrapper/RCTWrapperView.h index f1ba1dbd7d9..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperView.h +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperView.h @@ -6,37 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -typedef CGSize (^RCTWrapperMeasureBlock)(CGSize minimumSize, CGSize maximumSize) - __attribute__((deprecated("This API will be removed along with the legacy architecture."))); - -@class RCTBridge; - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperView : UIView - -@property (nonatomic, retain, nullable) UIView *contentView; -@property (nonatomic, readonly) RCTWrapperMeasureBlock measureBlock; - -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - -#pragma mark - Restrictions - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; -- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE; - -- (void)addSubview:(UIView *)view NS_UNAVAILABLE; -- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index NS_UNAVAILABLE; -- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview NS_UNAVAILABLE; -- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperView.m b/packages/react-native/Libraries/Wrapper/RCTWrapperView.m index 18bd976e908..0fe01ea01dc 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperView.m +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperView.m @@ -6,94 +6,3 @@ */ #import "RCTWrapperView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -@implementation RCTWrapperView { - __weak RCTBridge *_bridge; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super initWithFrame:CGRectZero]) { - _bridge = bridge; - __weak __typeof(self) weakSelf = self; - - _measureBlock = ^(CGSize minimumSize, CGSize maximumSize) { - __typeof(self) strongSelf = weakSelf; - - if (!strongSelf) { - return maximumSize; - } - - CGSize size = [strongSelf sizeThatFits:maximumSize]; - - return CGSizeMake(MAX(size.width, minimumSize.width), MAX(size.height, minimumSize.height)); - }; - } - - return self; -} - -#pragma mark - `contentView` - -- (nullable UIView *)contentView -{ - return self.subviews.firstObject; -} - -- (void)setContentView:(UIView *)contentView -{ - while (self.subviews.firstObject) { - [self.subviews.firstObject removeFromSuperview]; - } - - if (!contentView) { - return; - } - - [super addSubview:contentView]; - - contentView.frame = self.bounds; - contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - contentView.translatesAutoresizingMaskIntoConstraints = YES; -} - -#pragma mark - Layout - -- (void)setNeedsLayout -{ - [super setNeedsLayout]; - [self invalidateIntrinsicContentSize]; -} - -- (void)invalidateIntrinsicContentSize -{ - [super invalidateIntrinsicContentSize]; - - // Setting `intrinsicContentSize` dirties the Yoga node and - // enforce Yoga to call `measure` function (backed to `measureBlock`). - [_bridge.uiManager setIntrinsicContentSize:self.intrinsicContentSize forView:self]; -} - -- (CGSize)intrinsicContentSize -{ - return [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - UIView *contentView = self.contentView; - if (!contentView) { - return [super sizeThatFits:size]; - } - - return [contentView sizeThatFits:size]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.h b/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.h index f63b1229de8..3f93aa3f360 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.h +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.h @@ -6,25 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperViewControllerHostingView : UIView - -@property (nonatomic, retain, nullable) UIViewController *contentViewController; - -#pragma mark - Restrictions - -- (void)addSubview:(UIView *)view NS_UNAVAILABLE; -- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index NS_UNAVAILABLE; -- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview NS_UNAVAILABLE; -- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.m b/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.m index e03059b486a..921a03a7843 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.m +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperViewControllerHostingView.m @@ -6,132 +6,3 @@ */ #import "RCTWrapperViewControllerHostingView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#pragma mark - UIViewController+Children - -@interface UIViewController (Children) - -@property (nonatomic, readonly) BOOL isAttached; -- (void)attachChildViewController:(UIViewController *)childViewController toContainerView:(UIView *)containerView; -- (void)detachChildViewController:(UIViewController *)childViewController; - -@end - -@implementation UIViewController (Children) - -- (BOOL)isAttached -{ - return self.parentViewController != nil; -} - -- (void)attachChildViewController:(UIViewController *)childViewController toContainerView:(UIView *)containerView -{ - [self addChildViewController:childViewController]; - // `[childViewController willMoveToParentViewController:self]` is calling automatically - [containerView addSubview:childViewController.view]; - childViewController.view.frame = containerView.bounds; - childViewController.view.translatesAutoresizingMaskIntoConstraints = YES; - childViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [childViewController didMoveToParentViewController:self]; - - [childViewController beginAppearanceTransition:true animated:false]; - [childViewController endAppearanceTransition]; -} - -- (void)detachChildViewController:(UIViewController *)childViewController -{ - [childViewController beginAppearanceTransition:false animated:false]; - [childViewController endAppearanceTransition]; - - [childViewController willMoveToParentViewController:nil]; - [childViewController.view removeFromSuperview]; - [childViewController removeFromParentViewController]; - // `[childViewController didMoveToParentViewController:nil]` is calling automatically -} - -@end - -@implementation RCTWrapperViewControllerHostingView { - UIViewController *_Nullable _contentViewController; -} - -#pragma mark - `contentViewController` - -- (nullable UIViewController *)contentViewController -{ - return _contentViewController; -} - -- (void)setContentViewController:(UIViewController *)contentViewController -{ - if (_contentViewController) { - [self detachContentViewControllerIfNeeded]; - } - - _contentViewController = contentViewController; - - if (_contentViewController) { - [self attachContentViewControllerIfNeeded]; - } -} - -#pragma mark - Attaching and Detaching - -- (void)attachContentViewControllerIfNeeded -{ - if (self.contentViewController.isAttached) { - return; - } - - [self.reactViewController attachChildViewController:self.contentViewController toContainerView:self]; -} - -- (void)detachContentViewControllerIfNeeded -{ - if (!self.contentViewController.isAttached) { - return; - } - - [self.reactViewController detachChildViewController:self.contentViewController]; -} - -#pragma mark - Life cycle - -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - if (newWindow == nil) { - [self detachContentViewControllerIfNeeded]; - } -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - [self attachContentViewControllerIfNeeded]; -} - -#pragma mark - Layout - -- (void)setNeedsLayout -{ - [super setNeedsLayout]; - [self.superview setNeedsLayout]; -} - -- (CGSize)intrinsicContentSize -{ - return self.contentViewController.view.intrinsicContentSize; -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - return [self.contentViewController.view sizeThatFits:size]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.h b/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.h index aec6421da42..b6bc9e2e3c2 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.h +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.h @@ -6,20 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@class RCTWrapperView; - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTWrapperViewManager : RCTViewManager - -- (RCTWrapperView *)view NS_REQUIRES_SUPER; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.m b/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.m index 6963b637c85..da625ec4115 100644 --- a/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.m +++ b/packages/react-native/Libraries/Wrapper/RCTWrapperViewManager.m @@ -6,26 +6,3 @@ */ #import "RCTWrapperViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTWrapperShadowView.h" -#import "RCTWrapperView.h" - -@implementation RCTWrapperViewManager - -RCT_EXPORT_MODULE() - -- (RCTShadowView *)shadowView -{ - return [[RCTWrapperShadowView alloc] initWithBridge:self.bridge]; -} - -- (UIView *)view -{ - return [[RCTWrapperView alloc] initWithBridge:self.bridge]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTBridge+Private.h b/packages/react-native/React/Base/RCTBridge+Private.h index 3a69f317ebf..59d2a2f3543 100644 --- a/packages/react-native/React/Base/RCTBridge+Private.h +++ b/packages/react-native/React/Base/RCTBridge+Private.h @@ -16,16 +16,6 @@ RCT_EXTERN void RCTRegisterModule(Class); @interface RCTBridge () -#ifndef RCT_REMOVE_LEGACY_ARCH -// Private designated initializer -- (instancetype)initWithDelegate:(id)delegate - bundleURL:(NSURL *)bundleURL - moduleProvider:(RCTBridgeModuleListProvider)block - launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER - __deprecated_msg("This API will be removed along with the legacy architecture."); - -#endif // RCT_REMOVE_LEGACY_ARCH - // Used for the profiler flow events between JS and native @property (nonatomic, assign) int64_t flowID; @property (nonatomic, assign) CFMutableDictionaryRef flowIDMap; @@ -150,9 +140,4 @@ RCT_EXTERN void RCTRegisterModule(Class); // TODO(cjhopman): this seems unsafe unless we require that it is only called on the main js queue. @property (nonatomic, readonly) void *runtime; -#ifndef RCT_REMOVE_LEGACY_ARCH -- (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER - __deprecated_msg("This API will be removed along with the legacy architecture."); -#endif // RCT_REMOVE_LEGACY_ARCH - @end diff --git a/packages/react-native/React/Base/RCTBridge.mm b/packages/react-native/React/Base/RCTBridge.mm index 661f8f0fa64..c8f8ad7c43b 100644 --- a/packages/react-native/React/Base/RCTBridge.mm +++ b/packages/react-native/React/Base/RCTBridge.mm @@ -253,433 +253,6 @@ void RCTUIManagerSetDispatchAccessibilityManagerInitOntoMain(BOOL enabled) kDispatchAccessibilityManagerInitOntoMain = enabled; } -#ifndef RCT_REMOVE_LEGACY_ARCH -class RCTBridgeHostTargetDelegate : public facebook::react::jsinspector_modern::HostTargetDelegate { - public: - RCTBridgeHostTargetDelegate(RCTBridge *bridge) - : bridge_(bridge), - pauseOverlayController_([[RCTPausedInDebuggerOverlayController alloc] init]), - networkHelper_([[RCTInspectorNetworkHelper alloc] init]) - { - } - - facebook::react::jsinspector_modern::HostTargetMetadata getMetadata() override - { - auto metadata = [RCTInspectorUtils getHostMetadata]; - - return { - .appDisplayName = [metadata.appDisplayName UTF8String], - .appIdentifier = [metadata.appIdentifier UTF8String], - .deviceName = [metadata.deviceName UTF8String], - .integrationName = "iOS Bridge (RCTBridge)", - .platform = [metadata.platform UTF8String], - .reactNativeVersion = [metadata.reactNativeVersion UTF8String], - }; - } - - void onReload(const PageReloadRequest &request) override - { - RCTAssertMainQueue(); - RCTTriggerReloadCommandListeners(@"Reloading due to PageReloadRequest from DevTools."); - } - - void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest &request) override - { - RCTAssertMainQueue(); - if (!request.message.has_value()) { - [pauseOverlayController_ hide]; - } else { - __weak RCTBridge *bridgeWeak = bridge_; - [pauseOverlayController_ showWithMessage:@(request.message.value().c_str()) - onResume:^{ - RCTAssertMainQueue(); - RCTBridge *bridgeStrong = bridgeWeak; - if (!bridgeStrong) { - return; - } - if (!bridgeStrong.inspectorTarget) { - return; - } - bridgeStrong.inspectorTarget->sendCommand( - facebook::react::jsinspector_modern::HostCommand::DebuggerResume); - }]; - } - } - - void loadNetworkResource(const RCTInspectorLoadNetworkResourceRequest ¶ms, RCTInspectorNetworkExecutor executor) - override - { - [networkHelper_ loadNetworkResourceWithParams:params executor:executor]; - } - - private: - __weak RCTBridge *bridge_; - RCTPausedInDebuggerOverlayController *pauseOverlayController_; - RCTInspectorNetworkHelper *networkHelper_; -}; - -@interface RCTBridge () -@end - -@implementation RCTBridge { - NSURL *_delegateBundleURL; - - std::unique_ptr _inspectorHostDelegate; - std::shared_ptr _inspectorTarget; - std::optional _inspectorPageId; -} - -+ (void)initialize -{ - _RCTInitializeJSThreadConstantInternal(); -} - -static RCTBridge *RCTCurrentBridgeInstance = nil; - -/** - * The last current active bridge instance. This is set automatically whenever - * the bridge is accessed. It can be useful for static functions or singletons - * that need to access the bridge for purposes such as logging, but should not - * be relied upon to return any particular instance, due to race conditions. - */ -+ (instancetype)currentBridge -{ - return RCTCurrentBridgeInstance; -} - -+ (void)setCurrentBridge:(RCTBridge *)currentBridge -{ - RCTCurrentBridgeInstance = currentBridge; -} - -- (instancetype)initWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions -{ - return [self initWithDelegate:delegate bundleURL:nil moduleProvider:nil launchOptions:launchOptions]; -} - -- (instancetype)initWithBundleURL:(NSURL *)bundleURL - moduleProvider:(RCTBridgeModuleListProvider)block - launchOptions:(NSDictionary *)launchOptions -{ - return [self initWithDelegate:nil bundleURL:bundleURL moduleProvider:block launchOptions:launchOptions]; -} - -- (instancetype)initWithDelegate:(id)delegate - bundleURL:(NSURL *)bundleURL - moduleProvider:(RCTBridgeModuleListProvider)block - launchOptions:(NSDictionary *)launchOptions -{ - // Only enable this assertion in OSS -#if COCOAPODS - [RCTBridge throwIfOnLegacyArch]; -#endif - - if (self = [super init]) { - _delegate = delegate; - _bundleURL = bundleURL; - _moduleProvider = block; - _launchOptions = [launchOptions copy]; - _inspectorHostDelegate = std::make_unique(self); - - [self setUp]; - } - return self; -} - -// Wrap the exception throwing in a static method to avoid the warning: "The code following the exception will never be -// executed". This might create build failures internally where we treat warnings as errors. -+ (void)throwIfOnLegacyArch -{ - @throw [NSException - exceptionWithName:NSInternalInconsistencyException - reason: - @"You are trying to initialize the legacy architecture. This is not supported anymore and will be removed in the next version of React Native. Please use the New Architecture instead." - userInfo:nil]; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)init) - -- (void)dealloc -{ - RCTBridge *batchedBridge = self.batchedBridge; - /** - * This runs only on the main thread, but crashes the subclass - * RCTAssertMainQueue(); - */ - // NOTE: RCTCxxBridge will use _inspectorTarget during [self invalidate], so we must - // keep it alive until after the call returns. - [self invalidate]; - - // `invalidate` is asynchronous if we aren't on the main queue. Unregister - // the HostTarget on the main queue so that `invalidate` can complete safely - // in that case. - if (_inspectorPageId.has_value()) { - // Since we can't keep using `self` after dealloc, steal its inspector - // state into block-mutable variables - __block auto inspectorPageId = std::move(_inspectorPageId); - __block auto inspectorTarget = std::move(_inspectorTarget); - RCTExecuteOnMainQueue(^{ - facebook::react::jsinspector_modern::getInspectorInstance().removePage(*inspectorPageId); - inspectorPageId.reset(); - // NOTE: RCTBridgeHostTargetDelegate holds a weak reference to RCTBridge. - // Conditionally call `inspectorTarget.reset()` to avoid a crash. - if (batchedBridge) { - [batchedBridge - dispatchBlock:^{ - inspectorTarget.reset(); - } - queue:RCTJSThread]; - } else { - inspectorTarget.reset(); - } - }); - } -} - -- (void)setRCTTurboModuleRegistry:(id)turboModuleRegistry -{ - [self.batchedBridge setRCTTurboModuleRegistry:turboModuleRegistry]; -} - -- (RCTBridgeModuleDecorator *)bridgeModuleDecorator -{ - return [self.batchedBridge bridgeModuleDecorator]; -} - -- (void)didReceiveReloadCommand -{ -#if RCT_ENABLE_INSPECTOR - auto &inspectorFlags = facebook::react::jsinspector_modern::InspectorFlags::getInstance(); - if (!inspectorFlags.getFuseboxEnabled()) { - // Disable debugger to resume the JsVM & avoid thread locks while reloading - [RCTInspectorDevServerHelper disableDebugger]; - } -#endif - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeWillReloadNotification object:self userInfo:nil]; - - /** - * Any thread - */ - dispatch_async(dispatch_get_main_queue(), ^{ - // WARNING: Invalidation is async, so it may not finish before re-setting up the bridge, - // causing some issues. TODO: revisit this post-Fabric/TurboModule. - [self invalidate]; - // Reload is a special case, do not preserve launchOptions and treat reload as a fresh start - self->_launchOptions = nil; - [self setUp]; - }); -} - -- (RCTModuleRegistry *)moduleRegistry -{ - return self.batchedBridge.moduleRegistry; -} - -- (NSArray *)moduleClasses -{ - return self.batchedBridge.moduleClasses; -} - -- (id)moduleForName:(NSString *)moduleName -{ - return [self.batchedBridge moduleForName:moduleName]; -} - -- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad -{ - return [self.batchedBridge moduleForName:moduleName lazilyLoadIfNecessary:lazilyLoad]; -} - -- (id)moduleForClass:(Class)moduleClass -{ - id module = [self.batchedBridge moduleForClass:moduleClass]; - if (!module) { - module = [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)]; - } - return module; -} - -- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol -{ - NSMutableArray *modules = [NSMutableArray new]; - for (Class moduleClass in [self.moduleClasses copy]) { - if ([moduleClass conformsToProtocol:protocol]) { - id module = [self moduleForClass:moduleClass]; - if (module) { - [modules addObject:module]; - } - } - } - return [modules copy]; -} - -- (BOOL)moduleIsInitialized:(Class)moduleClass -{ - return [self.batchedBridge moduleIsInitialized:moduleClass]; -} - -/** - * DEPRECATED - please use RCTReloadCommand. - */ -- (void)reload -{ - RCTTriggerReloadCommandListeners(@"Unknown from bridge"); -} - -/** - * DEPRECATED - please use RCTReloadCommand. - */ -- (void)reloadWithReason:(NSString *)reason -{ - RCTTriggerReloadCommandListeners(reason); -} - -- (void)onFastRefresh -{ - [[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeFastRefreshNotification object:self]; -} - -/** - * DEPRECATED - please use RCTReloadCommand. - */ -- (void)requestReload -{ - [self reloadWithReason:@"Requested from bridge"]; -} - -- (Class)bridgeClass -{ - return [RCTCxxBridge class]; -} - -- (void)setUp -{ - RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil); - - _performanceLogger = [RCTPerformanceLogger new]; - [_performanceLogger markStartForTag:RCTPLInitReactRuntime]; - [_performanceLogger markStartForTag:RCTPLBridgeStartup]; - [_performanceLogger markStartForTag:RCTPLTTI]; - - auto &inspectorFlags = facebook::react::jsinspector_modern::InspectorFlags::getInstance(); - if (inspectorFlags.getFuseboxEnabled() && !_inspectorPageId.has_value()) { - _inspectorTarget = - facebook::react::jsinspector_modern::HostTarget::create(*_inspectorHostDelegate, [](auto callback) { - RCTExecuteOnMainQueue(^{ - callback(); - }); - }); - __weak RCTBridge *weakSelf = self; - _inspectorPageId = facebook::react::jsinspector_modern::getInspectorInstance().addPage( - "React Native Bridge", - /* vm */ "", - [weakSelf](std::unique_ptr remote) - -> std::unique_ptr { - RCTBridge *strongSelf = weakSelf; - if (!strongSelf) { - // This can happen if we're about to be dealloc'd. Reject the connection. - return nullptr; - } - return strongSelf->_inspectorTarget->connect(std::move(remote)); - }, - {.nativePageReloads = true}); - } - - Class bridgeClass = self.bridgeClass; - - // Only update bundleURL from delegate if delegate bundleURL has changed - NSURL *previousDelegateURL = _delegateBundleURL; - _delegateBundleURL = [self.delegate sourceURLForBridge:self]; - if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) { - _bundleURL = _delegateBundleURL; - } - - // Sanitize the bundle URL - _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString]; - - RCTExecuteOnMainQueue(^{ - RCTRegisterReloadCommandListener(self); - RCTReloadCommandSetBundleURL(self->_bundleURL); - }); - - self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self]; - [self.batchedBridge start]; - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -- (BOOL)isLoading -{ - return self.batchedBridge.loading; -} - -- (BOOL)isValid -{ - return self.batchedBridge.valid; -} - -- (BOOL)isBatchActive -{ - return [_batchedBridge isBatchActive]; -} - -- (void)invalidate -{ - [[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeWillBeInvalidatedNotification object:self]; - - RCTBridge *batchedBridge = self.batchedBridge; - self.batchedBridge = nil; - - if (batchedBridge) { - RCTExecuteOnMainQueue(^{ - [batchedBridge invalidate]; - }); - } -} - -- (void)updateModuleWithInstance:(id)instance -{ - [self.batchedBridge updateModuleWithInstance:instance]; -} - -- (void)registerAdditionalModuleClasses:(NSArray *)modules -{ - [self.batchedBridge registerAdditionalModuleClasses:modules]; -} - -- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args -{ - NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."]; - NSString *module = ids[0]; - NSString *method = ids[1]; - [self enqueueJSCall:module method:method args:args completion:NULL]; -} - -- (void)enqueueJSCall:(NSString *)module - method:(NSString *)method - args:(NSArray *)args - completion:(dispatch_block_t)completion -{ - [self.batchedBridge enqueueJSCall:module method:method args:args completion:completion]; -} - -- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args -{ - [self.batchedBridge enqueueCallback:cbID args:args]; -} - -- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path -{ - [self.batchedBridge registerSegmentWithId:segmentId path:path]; -} - -- (facebook::react::jsinspector_modern::HostTarget *)inspectorTarget -{ - return _inspectorTarget.get(); -} -@end -#else // RCT_REMOVE_LEGACY_ARCH @implementation RCTBridge - (instancetype)initWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions { @@ -785,4 +358,3 @@ + (instancetype)currentBridge } @end -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTBridgeDelegate.h b/packages/react-native/React/Base/RCTBridgeDelegate.h index 7e795581fa4..d5293d9f9db 100644 --- a/packages/react-native/React/Base/RCTBridgeDelegate.h +++ b/packages/react-native/React/Base/RCTBridgeDelegate.h @@ -13,72 +13,6 @@ NS_ASSUME_NONNULL_BEGIN @protocol RCTBridgeDelegate -#ifndef RCT_REMOVE_LEGACY_ARCH - -/** - * The location of the JavaScript source file. When running from the packager - * this should be an absolute URL, e.g. `http://localhost:8081/index.ios.bundle`. - * When running from a locally bundled JS file, this should be a `file://` url - * pointing to a path inside the app resources, e.g. `file://.../main.jsbundle`. - */ -- (NSURL *__nullable)sourceURLForBridge:(RCTBridge *)bridge - __deprecated_msg("This API will be removed along with the legacy architecture."); - -@optional - -/** - * The bridge initializes any registered RCTBridgeModules automatically, however - * if you wish to instantiate your own module instances, you can return them - * from this method. - * - * Note: You should always return a new instance for each call, rather than - * returning the same instance each time the bridge is reloaded. Module instances - * should not be shared between bridges, and this may cause unexpected behavior. - * - * It is also possible to override standard modules with your own implementations - * by returning a class with the same `moduleName` from this method, but this is - * not recommended in most cases - if the module methods and behavior do not - * match exactly, it may lead to bugs or crashes. - */ -- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * The bridge will call this method when a module been called from JS - * cannot be found among registered modules. - * It should return YES if the module with name 'moduleName' was registered - * in the implementation, and the system must attempt to look for it again among registered. - * If the module was not registered, return NO to prevent further searches. - */ -- (BOOL)bridge:(RCTBridge *)bridge - didNotFindModule:(NSString *)moduleName - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * The bridge will automatically attempt to load the JS source code from the - * location specified by the `sourceURLForBridge:` method, however, if you want - * to handle loading the JS yourself, you can do so by implementing this method. - */ -- (void)loadSourceForBridge:(RCTBridge *)bridge - onProgress:(RCTSourceLoadProgressBlock)onProgress - onComplete:(RCTSourceLoadBlock)loadCallback - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Similar to loadSourceForBridge:onProgress:onComplete: but without progress - * reporting. - */ -- (void)loadSourceForBridge:(RCTBridge *)bridge - withBlock:(RCTSourceLoadBlock)loadCallback - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Retrieve the list of lazy-native-modules names for the given bridge. - */ -- (NSDictionary *)extraLazyModuleClassesForBridge:(RCTBridge *)bridge - __deprecated_msg("This API will be removed along with the legacy architecture."); - -#endif // RCT_REMOVE_LEGACY_ARCH @end NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/RCTBridgeModule.h b/packages/react-native/React/Base/RCTBridgeModule.h index 940e64f4dd0..20b943538c8 100644 --- a/packages/react-native/React/Base/RCTBridgeModule.h +++ b/packages/react-native/React/Base/RCTBridgeModule.h @@ -368,9 +368,6 @@ RCT_EXTERN_C_END * A class that allows NativeModules and TurboModules to look up one another. */ @interface RCTModuleRegistry : NSObject -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge; -#endif // RCT_REMOVE_LEGACY_ARCH - (void)setTurboModuleRegistry:(id)turboModuleRegistry; - (id)moduleForName:(const char *)moduleName; @@ -389,9 +386,6 @@ typedef void (^RCTViewRegistryUIBlock)(RCTViewRegistry *viewRegistry); * A class that allows NativeModules to query for views, given React Tags. */ @interface RCTViewRegistry : NSObject -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge; -#endif // RCT_REMOVE_LEGACY_ARCH - (void)setBridgelessComponentViewProvider:(RCTBridgelessComponentViewProvider)bridgelessComponentViewProvider; - (UIView *)viewForReactTag:(NSNumber *)reactTag; @@ -409,9 +403,6 @@ typedef void (^RCTBridgelessJSModuleMethodInvoker)( * as callable with React Native. */ @interface RCTCallableJSModules : NSObject -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge; -#endif // RCT_REMOVE_LEGACY_ARCH - (void)setBridgelessJSModuleMethodInvoker:(RCTBridgelessJSModuleMethodInvoker)bridgelessJSModuleMethodInvoker; - (void)invokeModule:(NSString *)moduleName method:(NSString *)methodName withArgs:(NSArray *)args; diff --git a/packages/react-native/React/Base/RCTBridgeProxy.mm b/packages/react-native/React/Base/RCTBridgeProxy.mm index c840503be2b..af1e6809a60 100644 --- a/packages/react-native/React/Base/RCTBridgeProxy.mm +++ b/packages/react-native/React/Base/RCTBridgeProxy.mm @@ -308,19 +308,6 @@ - (void)start cmd:_cmd]; } -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)registerModuleForFrameUpdates:(id)module withModuleData:(RCTModuleData *)moduleData -{ - [self logError:@"This method is not supported. Nooping" cmd:_cmd]; -} - -- (RCTModuleData *)moduleDataForName:(NSString *)moduleName -{ - [self logError:@"This method is not supported. Returning nil." cmd:_cmd]; - return nil; -} -#endif // RCT_REMOVE_LEGACY_ARCH - - (void)registerAdditionalModuleClasses:(NSArray *)newModules { [self diff --git a/packages/react-native/React/Base/RCTBundleManager.h b/packages/react-native/React/Base/RCTBundleManager.h index 268c8689863..db2c1813e2a 100644 --- a/packages/react-native/React/Base/RCTBundleManager.h +++ b/packages/react-native/React/Base/RCTBundleManager.h @@ -67,9 +67,6 @@ typedef NSMutableArray *_Nullable (^RCTPackagerOptionsUpdater) - (nullable instancetype)initWithBundleConfig:(nullable RCTBundleConfiguration *)bundleConfig; -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(nullable RCTBridge *)bridge; -#endif // RCT_REMOVE_LEGACY_ARCH - (void)setBridgelessBundleURLGetter:(nullable RCTBridgelessBundleURLGetter)getter andSetter:(nullable RCTBridgelessBundleURLSetter)setter andDefaultGetter:(nullable RCTBridgelessBundleURLGetter)defaultGetter; diff --git a/packages/react-native/React/Base/RCTBundleManager.m b/packages/react-native/React/Base/RCTBundleManager.m index 88d2217efcf..fd5d6b3977a 100644 --- a/packages/react-native/React/Base/RCTBundleManager.m +++ b/packages/react-native/React/Base/RCTBundleManager.m @@ -102,9 +102,6 @@ - (NSURL *)getBundleURL @end @implementation RCTBundleManager { -#ifndef RCT_REMOVE_LEGACY_ARCH - __weak RCTBridge *_bridge; -#endif // RCT_REMOVE_LEGACY_ARCH RCTBridgelessBundleURLGetter _bridgelessBundleURLGetter; RCTBridgelessBundleURLSetter _bridgelessBundleURLSetter; RCTBridgelessBundleURLGetter _bridgelessBundleURLDefaultGetter; @@ -124,13 +121,6 @@ - (instancetype)init return [self initWithBundleConfig:[RCTBundleConfiguration defaultConfiguration]]; } -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge -{ - _bridge = bridge; -} -#endif // RCT_REMOVE_LEGACY_ARCH - - (void)setBridgelessBundleURLGetter:(RCTBridgelessBundleURLGetter)getter andSetter:(RCTBridgelessBundleURLSetter)setter andDefaultGetter:(RCTBridgelessBundleURLGetter)defaultGetter @@ -142,13 +132,6 @@ - (void)setBridgelessBundleURLGetter:(RCTBridgelessBundleURLGetter)getter - (void)setBundleURL:(NSURL *)bundleURL { -#ifndef RCT_REMOVE_LEGACY_ARCH - if (_bridge) { - _bridge.bundleURL = bundleURL; - return; - } -#endif // RCT_REMOVE_LEGACY_ARCH - RCTAssert( _bridgelessBundleURLSetter != nil, @"RCTBundleManager: In bridgeless mode, RCTBridgelessBundleURLSetter must not be nil."); @@ -157,12 +140,6 @@ - (void)setBundleURL:(NSURL *)bundleURL - (NSURL *)bundleURL { -#ifndef RCT_REMOVE_LEGACY_ARCH - if (_bridge) { - return _bridge.bundleURL; - } -#endif // RCT_REMOVE_LEGACY_ARCH - RCTAssert( _bridgelessBundleURLGetter != nil, @"RCTBundleManager: In bridgeless mode, RCTBridgelessBundleURLGetter must not be nil."); @@ -178,14 +155,6 @@ - (NSURL *)bundleURL - (void)resetBundleURL { -#ifndef RCT_REMOVE_LEGACY_ARCH - RCTBridge *strongBridge = _bridge; - if (strongBridge) { - strongBridge.bundleURL = [strongBridge.delegate sourceURLForBridge:strongBridge]; - return; - } -#endif // RCT_REMOVE_LEGACY_ARCH - RCTAssert( _bridgelessBundleURLDefaultGetter != nil, @"RCTBundleManager: In bridgeless mode, default RCTBridgelessBundleURLGetter must not be nil."); diff --git a/packages/react-native/React/Base/RCTCallableJSModules.m b/packages/react-native/React/Base/RCTCallableJSModules.m index 669f683c564..57d9acf940e 100644 --- a/packages/react-native/React/Base/RCTCallableJSModules.m +++ b/packages/react-native/React/Base/RCTCallableJSModules.m @@ -10,18 +10,8 @@ @implementation RCTCallableJSModules { RCTBridgelessJSModuleMethodInvoker _bridgelessJSModuleMethodInvoker; -#ifndef RCT_REMOVE_LEGACY_ARCH - __weak RCTBridge *_bridge; -#endif // RCT_REMOVE_LEGACY_ARCH } -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge -{ - _bridge = bridge; -} -#endif // RCT_REMOVE_LEGACY_ARCH - - (void)setBridgelessJSModuleMethodInvoker:(RCTBridgelessJSModuleMethodInvoker)bridgelessJSModuleMethodInvoker { _bridgelessJSModuleMethodInvoker = bridgelessJSModuleMethodInvoker; @@ -37,14 +27,6 @@ - (void)invokeModule:(NSString *)moduleName withArgs:(NSArray *)args onComplete:(dispatch_block_t)onComplete { -#ifndef RCT_REMOVE_LEGACY_ARCH - RCTBridge *bridge = _bridge; - if (bridge) { - [bridge enqueueJSCall:moduleName method:methodName args:args completion:onComplete]; - return; - } -#endif // RCT_REMOVE_LEGACY_ARCH - if (_bridgelessJSModuleMethodInvoker) { _bridgelessJSModuleMethodInvoker(moduleName, methodName, args, onComplete); } diff --git a/packages/react-native/React/Base/RCTJavaScriptExecutor.h b/packages/react-native/React/Base/RCTJavaScriptExecutor.h index 49730b08334..965d78e7361 100644 --- a/packages/react-native/React/Base/RCTJavaScriptExecutor.h +++ b/packages/react-native/React/Base/RCTJavaScriptExecutor.h @@ -14,72 +14,3 @@ typedef void (^RCTJavaScriptCompleteBlock)(NSError *__strong) __deprecated_msg("This api will be removed along with the bridge."); typedef void (^RCTJavaScriptCallback)(__strong id, NSError *__strong) __deprecated_msg("This api will be removed along with the bridge."); - -#ifndef RCT_REMOVE_LEGACY_ARCH -/** - * Abstracts away a JavaScript execution context - we may be running code in a - * web view (for debugging purposes), or may be running code in a `JSContext`. - */ -__deprecated_msg("This api will be removed along with the bridge.") - @protocol RCTJavaScriptExecutor - -/** - * Used to set up the executor after the bridge has been fully initialized. - * Do any expensive setup in this method instead of `-init`. - */ -- (void)setUp; - -/** - * Whether the executor has been invalidated - */ -@property (nonatomic, readonly, getter=isValid) BOOL valid; - -/** - * Executes BatchedBridge.flushedQueue on JS thread and calls the given callback - * with JSValue, containing the next queue, and JSContext. - */ -- (void)flushedQueue:(RCTJavaScriptCallback)onComplete; - -/** - * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module name, - * method name and optional additional arguments on the JS thread and calls the - * given callback with JSValue, containing the next queue, and JSContext. - */ -- (void)callFunctionOnModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)args - callback:(RCTJavaScriptCallback)onComplete; - -/** - * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID, - * and optional additional arguments on the JS thread and calls the - * given callback with JSValue, containing the next queue, and JSContext. - */ -- (void)invokeCallbackID:(NSNumber *)cbID arguments:(NSArray *)args callback:(RCTJavaScriptCallback)onComplete; - -/** - * Runs an application script, and notifies of the script load being complete via `onComplete`. - */ -- (void)executeApplicationScript:(NSData *)script - sourceURL:(NSURL *)sourceURL - onComplete:(RCTJavaScriptCompleteBlock)onComplete; - -- (void)injectJSONText:(NSString *)script - asGlobalObjectNamed:(NSString *)objectName - callback:(RCTJavaScriptCompleteBlock)onComplete; - -/** - * Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async` - * on the main queue if the executor doesn't own a thread. - */ -- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block; - -/** - * Special case for Timers + ContextExecutor - instead of the default - * if jsthread then call else dispatch call on jsthread - * ensure the call is made async on the jsthread - */ -- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block; - -@end -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTModuleData.mm b/packages/react-native/React/Base/RCTModuleData.mm index 918261456be..921f24fcf5c 100644 --- a/packages/react-native/React/Base/RCTModuleData.mm +++ b/packages/react-native/React/Base/RCTModuleData.mm @@ -7,488 +7,6 @@ #import "RCTModuleData.h" -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -#import - -#import "RCTBridge+Private.h" -#import "RCTBridge.h" -#import "RCTBridgeModuleDecorator.h" -#import "RCTCallInvokerModule.h" -#import "RCTConstants.h" -#import "RCTInitializing.h" -#import "RCTLog.h" -#import "RCTModuleMethod.h" -#import "RCTProfile.h" -#import "RCTUtils.h" - -using namespace facebook::react; - -namespace { -int32_t getUniqueId() -{ - static std::atomic counter{0}; - return counter++; -} -} // namespace - -@implementation RCTModuleData { - NSDictionary *_constantsToExport; - NSString *_queueName; - __weak RCTBridge *_bridge; - RCTBridgeModuleProvider _moduleProvider; - std::mutex _instanceLock; - BOOL _setupComplete; - RCTModuleRegistry *_moduleRegistry; - RCTViewRegistry *_viewRegistry_DEPRECATED; - RCTBundleManager *_bundleManager; - RCTCallableJSModules *_callableJSModules; - BOOL _isInitialized; -} - -@synthesize methods = _methods; -@synthesize methodsByName = _methodsByName; -@synthesize instance = _instance; -@synthesize methodQueue = _methodQueue; - -- (void)_setUpWithBridge:(RCTBridge *)bridge - moduleRegistry:(RCTModuleRegistry *)moduleRegistry - viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED - bundleManager:(RCTBundleManager *)bundleManager - callableJSModules:(RCTCallableJSModules *)callableJSModules -{ - // start with the ivars - { - _bridge = bridge; - _moduleRegistry = moduleRegistry; - _viewRegistry_DEPRECATED = viewRegistry_DEPRECATED; - _bundleManager = bundleManager; - _callableJSModules = callableJSModules; - } - - _implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)]; - - // If a module overrides `constantsToExport` and doesn't implement `requiresMainQueueSetup`, then we must assume - // that it must be called on the main thread, because it may need to access UIKit. - _hasConstantsToExport = [_moduleClass instancesRespondToSelector:@selector(constantsToExport)]; - - const BOOL implementsRequireMainQueueSetup = [_moduleClass respondsToSelector:@selector(requiresMainQueueSetup)]; - if (implementsRequireMainQueueSetup) { - _requiresMainQueueSetup = [_moduleClass requiresMainQueueSetup]; - } else { - static IMP objectInitMethod; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)]; - }); - - // If a module overrides `init` then we must assume that it expects to be - // initialized on the main thread, because it may need to access UIKit. - const BOOL hasCustomInit = - !_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod; - - _requiresMainQueueSetup = _hasConstantsToExport || hasCustomInit; - } -} - -- (instancetype)initWithModuleClass:(Class)moduleClass - bridge:(RCTBridge *)bridge - moduleRegistry:(RCTModuleRegistry *)moduleRegistry - viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED - bundleManager:(RCTBundleManager *)bundleManager - callableJSModules:(RCTCallableJSModules *)callableJSModules -{ - if (self = [super init]) { - _moduleClass = moduleClass; - _moduleProvider = [^id { - return [moduleClass new]; - } copy]; - [self _setUpWithBridge:bridge - moduleRegistry:moduleRegistry - viewRegistry_DEPRECATED:viewRegistry_DEPRECATED - bundleManager:bundleManager - callableJSModules:callableJSModules]; - } - return self; -} - -- (instancetype)initWithModuleInstance:(id)instance - bridge:(RCTBridge *)bridge - moduleRegistry:(RCTModuleRegistry *)moduleRegistry - viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED - bundleManager:(RCTBundleManager *)bundleManager - callableJSModules:(RCTCallableJSModules *)callableJSModules -{ - if (self = [super init]) { - _instance = instance; - _moduleClass = [instance class]; - [self _setUpWithBridge:bridge - moduleRegistry:moduleRegistry - viewRegistry_DEPRECATED:viewRegistry_DEPRECATED - bundleManager:bundleManager - callableJSModules:callableJSModules]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)init); - -#pragma mark - private setup methods - -- (void)setUpInstanceAndBridge:(int32_t)requestId -{ - NSString *moduleName = [self name]; - - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, - @"[RCTModuleData setUpInstanceAndBridge]", - @{@"moduleClass" : NSStringFromClass(_moduleClass)}); - { - std::unique_lock lock(_instanceLock); - BOOL shouldSetup = !_setupComplete && _bridge.valid; - - if (shouldSetup) { - if (!_instance) { - if (RCT_DEBUG && _requiresMainQueueSetup) { - RCTAssertMainQueue(); - } - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setUpInstanceAndBridge] Create module", nil); - - BridgeNativeModulePerfLogger::moduleCreateConstructStart([moduleName UTF8String], requestId); - _instance = _moduleProvider ? _moduleProvider() : nil; - BridgeNativeModulePerfLogger::moduleCreateConstructEnd([moduleName UTF8String], requestId); - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - if (!_instance) { - // Module init returned nil, probably because automatic instantiation - // of the module is not supported, and it is supposed to be passed in to - // the bridge constructor. Mark setup complete to avoid doing more work. - _setupComplete = YES; - RCTLogWarn( - @"The module %@ is returning nil from its constructor. You " - "may need to instantiate it yourself and pass it into the " - "bridge.", - _moduleClass); - } - } - - if (_instance && RCTProfileIsProfiling()) { - RCTProfileHookInstance(_instance); - } - } - - if (_instance) { - BridgeNativeModulePerfLogger::moduleCreateSetUpStart([moduleName UTF8String], requestId); - } - - if (shouldSetup) { - // Bridge must be set before methodQueue is set up, as methodQueue - // initialization requires it (View Managers get their queue by calling - // self.bridge.uiManager.methodQueue) - [self setBridgeForInstance]; - - RCTBridgeModuleDecorator *moduleDecorator = - [[RCTBridgeModuleDecorator alloc] initWithViewRegistry:_viewRegistry_DEPRECATED - moduleRegistry:_moduleRegistry - bundleManager:_bundleManager - callableJSModules:_callableJSModules]; - [moduleDecorator attachInteropAPIsToModule:_instance]; - - // This is a more performant alternative for conformsToProtocol:@protocol(RCTCallInvokerModule) - if ([_instance respondsToSelector:@selector(setCallInvoker:)]) { - [(id)_instance setCallInvoker:[self.callInvokerProvider callInvokerForModuleData:self]]; - } - } - - [self setUpMethodQueue]; - - if (shouldSetup) { - [self _initializeModule]; - } - } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - // This is called outside of the lock in order to prevent deadlock issues - // because the logic in `finishSetupForInstance` can cause - // `moduleData.instance` to be accessed re-entrantly. - if (_bridge.moduleSetupComplete) { - [self finishSetupForInstance]; - } else { - // If we're here, then the module is completely initialized, - // except for what finishSetupForInstance does. When the instance - // method is called after moduleSetupComplete, - // finishSetupForInstance will run. If _requiresMainQueueSetup - // is true, getting the instance will block waiting for the main - // thread, which could take a while if the main thread is busy - // (I've seen 50ms in testing). So we clear that flag, since - // nothing in finishSetupForInstance needs to be run on the main - // thread. - _requiresMainQueueSetup = NO; - } - - if (_instance) { - BridgeNativeModulePerfLogger::moduleCreateSetUpEnd([moduleName UTF8String], requestId); - } -} - -- (void)setBridgeForInstance -{ - if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) { - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setBridgeForInstance]", nil); - @try { - [(id)_instance setValue:_bridge forKey:@"bridge"]; - } @catch (NSException *) { - RCTLogError( - @"%@ has no setter or ivar for its bridge, which is not " - "permitted. You must either @synthesize the bridge property, " - "or provide your own setter method.", - self.name); - } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } -} - -- (void)_initializeModule -{ - if (!_isInitialized && [_instance respondsToSelector:@selector(initialize)]) { - _isInitialized = YES; - [(id)_instance initialize]; - } -} - -- (void)finishSetupForInstance -{ - if (!_setupComplete && _instance) { - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData finishSetupForInstance]", nil); - _setupComplete = YES; - [_bridge registerModuleForFrameUpdates:_instance withModuleData:self]; - [[NSNotificationCenter defaultCenter] - postNotificationName:RCTDidInitializeModuleNotification - object:_bridge - userInfo:@{@"module" : _instance, @"bridge" : RCTNullIfNil(_bridge.parentBridge)}]; - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } -} - -- (void)setUpMethodQueue -{ - if (_instance && !_methodQueue && _bridge.valid) { - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setUpMethodQueue]", nil); - BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)]; - if (implementsMethodQueue && _bridge.valid) { - _methodQueue = _instance.methodQueue; - } - if (!_methodQueue && _bridge.valid) { - // Create new queue (store queueName, as it isn't retained by dispatch_queue) - _queueName = [NSString stringWithFormat:@"com.facebook.react.%@Queue", self.name]; - _methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL); - - // assign it to the module - if (implementsMethodQueue) { - @try { - [(id)_instance setValue:_methodQueue forKey:@"methodQueue"]; - } @catch (NSException *) { - RCTLogError( - @"%@ is returning nil for its methodQueue, which is not " - "permitted. You must either return a pre-initialized " - "queue, or @synthesize the methodQueue to let the bridge " - "create a queue for you.", - self.name); - } - } - } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } -} - -- (void)calculateMethods -{ - if (_methods && _methodsByName) { - return; - } - - NSMutableArray> *moduleMethods = [NSMutableArray new]; - NSMutableDictionary> *moduleMethodsByName = [NSMutableDictionary new]; - - if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) { - [moduleMethods addObjectsFromArray:[self.instance methodsToExport]]; - } - - unsigned int methodCount; - Class cls = _moduleClass; - while (cls && cls != [NSObject class] && cls != [NSProxy class]) { - Method *methods = class_copyMethodList(object_getClass(cls), &methodCount); - - for (unsigned int i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) { - IMP imp = method_getImplementation(method); - auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_moduleClass, selector); - id moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod - moduleClass:_moduleClass]; - - NSString *str = [NSString stringWithUTF8String:moduleMethod.JSMethodName]; - [moduleMethodsByName setValue:moduleMethod forKey:str]; - [moduleMethods addObject:moduleMethod]; - } - } - - free(methods); - cls = class_getSuperclass(cls); - } - - _methods = [moduleMethods copy]; - _methodsByName = [moduleMethodsByName copy]; -} - -#pragma mark - public getters - -- (BOOL)hasInstance -{ - std::unique_lock lock(_instanceLock); - return _instance != nil; -} - -- (id)instance -{ - NSString *moduleName = [self name]; - int32_t requestId = getUniqueId(); - BridgeNativeModulePerfLogger::moduleCreateStart([moduleName UTF8String], requestId); - - if (!_setupComplete) { - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData instanceForClass:%@]", _moduleClass]), nil); - if (_requiresMainQueueSetup) { - // The chances of deadlock here are low, because module init very rarely - // calls out to other threads, however we can't control when a module might - // get accessed by client code during bridge setup, and a very low risk of - // deadlock is better than a fairly high risk of an assertion being thrown. - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData instance] main thread setup", nil); - - if (!RCTIsMainQueue()) { - RCTLogWarn(@"RCTBridge required dispatch_sync to load %@. This may lead to deadlocks", _moduleClass); - } - - RCTUnsafeExecuteOnMainQueueSync(^{ - [self setUpInstanceAndBridge:requestId]; - }); - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } else { - [self setUpInstanceAndBridge:requestId]; - } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } else { - BridgeNativeModulePerfLogger::moduleCreateCacheHit([moduleName UTF8String], requestId); - } - - if (_instance) { - BridgeNativeModulePerfLogger::moduleCreateEnd([moduleName UTF8String], requestId); - } else { - BridgeNativeModulePerfLogger::moduleCreateFail([moduleName UTF8String], requestId); - } - return _instance; -} - -- (NSString *)name -{ - return RCTBridgeModuleNameForClass(_moduleClass); -} - -- (NSArray> *)methods -{ - [self calculateMethods]; - return _methods; -} - -- (NSDictionary> *)methodsByName -{ - [self calculateMethods]; - return _methodsByName; -} - -- (void)gatherConstants -{ - return [self gatherConstantsAndSignalJSRequireEnding:NO]; -} - -- (void)gatherConstantsAndSignalJSRequireEnding:(BOOL)startMarkers -{ - NSString *moduleName = [self name]; - - if (_hasConstantsToExport && !_constantsToExport) { - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil); - (void)[self instance]; - - if (startMarkers) { - /** - * Why do we instrument moduleJSRequireEndingStart here? - * - NativeModule requires from JS go through ModuleRegistry::getConfig(). - * - ModuleRegistry::getConfig() calls NativeModule::getConstants() first. - * - This delegates to RCTNativeModule::getConstants(), which calls RCTModuleData gatherConstants(). - * - Therefore, this is the first statement that executes after the NativeModule is created/initialized in a JS - * require. - */ - BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]); - } - - if (_requiresMainQueueSetup) { - if (!RCTIsMainQueue()) { - RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks", _moduleClass); - } - - RCTUnsafeExecuteOnMainQueueSync(^{ - self->_constantsToExport = [self->_instance constantsToExport] ?: @{}; - }); - } else { - _constantsToExport = [_instance constantsToExport] ?: @{}; - } - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } else if (startMarkers) { - /** - * If a NativeModule doesn't have constants, it isn't eagerly loaded until its methods are first invoked. - * Therefore, we should immediately start JSRequireEnding - */ - BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]); - } -} - -- (NSDictionary *)exportedConstants -{ - [self gatherConstantsAndSignalJSRequireEnding:YES]; - NSDictionary *constants = _constantsToExport; - _constantsToExport = nil; // Not needed anymore - return constants; -} - -- (dispatch_queue_t)methodQueue -{ - if (_bridge.valid) { - __unused id instance = self.instance; - RCTAssert(_methodQueue != nullptr, @"Module %@ has no methodQueue (instance: %@)", self, instance); - } - return _methodQueue; -} - -- (void)invalidate -{ - _methodQueue = nil; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; name=\"%@\">", [self class], self, self.name]; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH @implementation RCTModuleData - (instancetype)initWithModuleClass:(Class)moduleClass @@ -520,5 +38,3 @@ - (void)invalidate } @end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTModuleRegistry.m b/packages/react-native/React/Base/RCTModuleRegistry.m index 508b6152657..64f4d19d022 100644 --- a/packages/react-native/React/Base/RCTModuleRegistry.m +++ b/packages/react-native/React/Base/RCTModuleRegistry.m @@ -12,18 +12,8 @@ @implementation RCTModuleRegistry { __weak id _turboModuleRegistry; -#ifndef RCT_REMOVE_LEGACY_ARCH - __weak RCTBridge *_bridge; -#endif // RCT_REMOVE_LEGACY_ARCH } -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge -{ - _bridge = bridge; -} -#endif // RCT_REMOVE_LEGACY_ARCH - - (void)setTurboModuleRegistry:(id)turboModuleRegistry { _turboModuleRegistry = turboModuleRegistry; @@ -38,13 +28,6 @@ - (id)moduleForName:(const char *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyL { id module = nil; -#ifndef RCT_REMOVE_LEGACY_ARCH - RCTBridge *bridge = _bridge; - if (bridge) { - module = [bridge moduleForName: [NSString stringWithUTF8String:moduleName] lazilyLoadIfNecessary:lazilyLoad]; - } -#endif // RCT_REMOVE_LEGACY_ARCH - id turboModuleRegistry = _turboModuleRegistry; if (module == nil && turboModuleRegistry && (lazilyLoad || [turboModuleRegistry moduleIsInitialized:moduleName])) { module = [turboModuleRegistry moduleForName:moduleName]; @@ -55,14 +38,6 @@ - (id)moduleForName:(const char *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyL - (BOOL)moduleIsInitialized:(Class)moduleClass { -#ifndef RCT_REMOVE_LEGACY_ARCH - RCTBridge *bridge = _bridge; - - if (bridge) { - return [bridge moduleIsInitialized:moduleClass]; - } -#endif // RCT_REMOVE_LEGACY_ARCH - id turboModuleRegistry = _turboModuleRegistry; if (turboModuleRegistry) { NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); diff --git a/packages/react-native/React/Base/RCTRootContentView.h b/packages/react-native/React/Base/RCTRootContentView.h index 22cd5fe75bf..3f93aa3f360 100644 --- a/packages/react-native/React/Base/RCTRootContentView.h +++ b/packages/react-native/React/Base/RCTRootContentView.h @@ -6,38 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -@class RCTBridge; -@class RCTTouchHandler; - -@interface RCTRootContentView : RCTView - -@property (nonatomic, readonly, weak) - RCTBridge *bridge __deprecated_msg("This API will be removed along with the legacy architecture."); -@property (nonatomic, readonly, assign) - BOOL contentHasAppeared __deprecated_msg("This API will be removed along with the legacy architecture."); -@property (nonatomic, readonly, strong) - RCTTouchHandler *touchHandler __deprecated_msg("This API will be removed along with the legacy architecture."); -@property (nonatomic, readonly, assign) - CGSize availableSize __deprecated_msg("This API will be removed along with the legacy architecture."); - -@property (nonatomic, assign) - BOOL passThroughTouches __deprecated_msg("This API will be removed along with the legacy architecture."); -@property (nonatomic, assign) RCTRootViewSizeFlexibility sizeFlexibility __deprecated_msg( - "This API will be removed along with the legacy architecture."); - -- (instancetype)initWithFrame:(CGRect)frame - bridge:(RCTBridge *)bridge - reactTag:(NSNumber *)reactTag - sizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility NS_DESIGNATED_INITIALIZER - __deprecated_msg("This API will be removed along with the legacy architecture."); - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTRootContentView.m b/packages/react-native/React/Base/RCTRootContentView.m index c3214f559da..efe6512e6a4 100644 --- a/packages/react-native/React/Base/RCTRootContentView.m +++ b/packages/react-native/React/Base/RCTRootContentView.m @@ -6,106 +6,3 @@ */ #import "RCTRootContentView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTBridge.h" -#import "RCTPerformanceLogger.h" -#import "RCTRootView.h" -#import "RCTRootViewInternal.h" -#import "RCTTouchHandler.h" -#import "RCTUIManager.h" -#import "UIView+React.h" - -@implementation RCTRootContentView - -- (instancetype)initWithFrame:(CGRect)frame - bridge:(RCTBridge *)bridge - reactTag:(NSNumber *)reactTag - sizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility -{ - if ((self = [super initWithFrame:frame])) { - _bridge = bridge; - self.reactTag = reactTag; - _sizeFlexibility = sizeFlexibility; - _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge]; - [_touchHandler attachToView:self]; - [_bridge.uiManager registerRootView:self]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (nonnull NSCoder *)aDecoder) - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [self updateAvailableSize]; -} - -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:subview atIndex:atIndex]; - [_bridge.performanceLogger markStopForTag:RCTPLTTI]; - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self->_contentHasAppeared) { - self->_contentHasAppeared = YES; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTContentDidAppearNotification object:self.superview]; - } - }); -} - -- (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility -{ - if (_sizeFlexibility == sizeFlexibility) { - return; - } - - _sizeFlexibility = sizeFlexibility; - [self setNeedsLayout]; -} - -- (CGSize)availableSize -{ - CGSize size = self.bounds.size; - return CGSizeMake( - _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? INFINITY : size.width, - _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? INFINITY : size.height); -} - -- (void)updateAvailableSize -{ - if (!self.reactTag || !_bridge.isValid) { - return; - } - - [_bridge.uiManager setAvailableSize:self.availableSize forRootView:self]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - // The root content view itself should never receive touches - UIView *hitView = [super hitTest:point withEvent:event]; - if (_passThroughTouches && hitView == self) { - return nil; - } - return hitView; -} - -- (void)invalidate -{ - if (self.userInteractionEnabled) { - self.userInteractionEnabled = NO; - [(RCTRootView *)self.superview contentViewInvalidated]; - - [_bridge enqueueJSCall:@"AppRegistry" - method:@"unmountApplicationComponentAtRootTag" - args:@[ self.reactTag ] - completion:NULL]; - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTRootView.m b/packages/react-native/React/Base/RCTRootView.m index 1cad581b9d9..1b1a3c47a5f 100644 --- a/packages/react-native/React/Base/RCTRootView.m +++ b/packages/react-native/React/Base/RCTRootView.m @@ -30,377 +30,6 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification"; -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTRootView { - RCTBridge *_bridge; - NSString *_moduleName; - RCTRootContentView *_contentView; - BOOL _passThroughTouches; - CGSize _intrinsicContentSize; -} - -- (instancetype)initWithFrame:(CGRect)frame - bridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties -{ - RCTAssertMainQueue(); - RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView"); - RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView"); - - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTRootView init]", nil); - if (!bridge.isLoading) { - [bridge.performanceLogger markStartForTag:RCTPLTTI]; - } - - if (self = [super initWithFrame:frame]) { - self.backgroundColor = [UIColor whiteColor]; - - _bridge = bridge; - _moduleName = moduleName; - _appProperties = [initialProperties copy]; - _loadingViewFadeDelay = 0.25; - _loadingViewFadeDuration = 0.25; - _sizeFlexibility = RCTRootViewSizeFlexibilityNone; - _minimumSize = CGSizeZero; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(bridgeDidReload) - name:RCTJavaScriptWillStartLoadingNotification - object:_bridge]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(javaScriptDidLoad:) - name:RCTJavaScriptDidLoadNotification - object:_bridge]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(hideLoadingView) - name:RCTContentDidAppearNotification - object:self]; - - [self showLoadingView]; - - // Immediately schedule the application to be started. - // (Sometimes actual `_bridge` is already batched bridge here.) - [self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)]; - } - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - return self; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties -{ - return [self initWithFrame:CGRectZero bridge:bridge moduleName:moduleName initialProperties:initialProperties]; -} - -- (instancetype)initWithBundleURL:(NSURL *)bundleURL - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - launchOptions:(NSDictionary *)launchOptions -{ - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions]; - - return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) - -- (UIView *)view -{ - return self; -} - -#pragma mark - passThroughTouches - -- (BOOL)passThroughTouches -{ - return _contentView.passThroughTouches; -} - -- (void)setPassThroughTouches:(BOOL)passThroughTouches -{ - _passThroughTouches = passThroughTouches; - _contentView.passThroughTouches = passThroughTouches; -} - -#pragma mark - Layout - -- (CGSize)sizeThatFits:(CGSize)size -{ - CGSize fitSize = _intrinsicContentSize; - CGSize currentSize = self.bounds.size; - - // Following the current `size` and current `sizeFlexibility` policy. - fitSize = CGSizeMake( - _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? fitSize.width : currentSize.width, - _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? fitSize.height : currentSize.height); - - // Following the given size constraints. - fitSize = CGSizeMake(MIN(size.width, fitSize.width), MIN(size.height, fitSize.height)); - - return fitSize; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - _contentView.frame = self.bounds; - _loadingView.center = (CGPoint){CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)}; -} - -- (void)setMinimumSize:(CGSize)minimumSize -{ - if (CGSizeEqualToSize(_minimumSize, minimumSize)) { - return; - } - _minimumSize = minimumSize; - __block NSNumber *tag = self.reactTag; - __weak typeof(self) weakSelf = self; - RCTExecuteOnUIManagerQueue(^{ - __strong typeof(self) strongSelf = weakSelf; - if (strongSelf && strongSelf->_bridge.isValid) { - RCTRootShadowView *shadowView = (RCTRootShadowView *)[strongSelf->_bridge.uiManager shadowViewForReactTag:tag]; - shadowView.minimumSize = minimumSize; - } - }); -} - -- (UIViewController *)reactViewController -{ - return _reactViewController ?: [super reactViewController]; -} - -- (BOOL)canBecomeFirstResponder -{ - return YES; -} - -- (void)setLoadingView:(UIView *)loadingView -{ - _loadingView = loadingView; - if (!_contentView.contentHasAppeared) { - [self showLoadingView]; - } -} - -- (void)showLoadingView -{ - if (_loadingView && !_contentView.contentHasAppeared) { - _loadingView.hidden = NO; - [self addSubview:_loadingView]; - } -} - -- (void)hideLoadingView -{ - if (_loadingView.superview == self && _contentView.contentHasAppeared) { - if (_loadingViewFadeDuration > 0) { - dispatch_after( - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_loadingViewFadeDelay * NSEC_PER_SEC)), - dispatch_get_main_queue(), - ^{ - [UIView transitionWithView:self - duration:self->_loadingViewFadeDuration - options:UIViewAnimationOptionTransitionCrossDissolve - animations:^{ - self->_loadingView.hidden = YES; - } - completion:^(__unused BOOL finished) { - [self->_loadingView removeFromSuperview]; - }]; - }); - } else { - _loadingView.hidden = YES; - [_loadingView removeFromSuperview]; - } - } -} - -- (NSNumber *)reactTag -{ - RCTAssertMainQueue(); - if (!super.reactTag) { - /** - * Every root view that is created must have a unique react tag. - * Numbering of these tags goes from 1, 11, 21, 31, etc - * - * NOTE: Since the bridge persists, the RootViews might be reused, so the - * react tag must be re-assigned every time a new UIManager is created. - */ - self.reactTag = RCTAllocateRootViewTag(); - } - return super.reactTag; -} - -- (void)bridgeDidReload -{ - RCTAssertMainQueue(); - // Clear the reactTag so it can be re-assigned - self.reactTag = nil; -} - -- (void)javaScriptDidLoad:(NSNotification *)notification -{ - RCTAssertMainQueue(); - - // Use the (batched) bridge that's sent in the notification payload, so the - // RCTRootContentView is scoped to the right bridge - RCTBridge *bridge = notification.userInfo[@"bridge"]; - if (bridge != _contentView.bridge) { - [self bundleFinishedLoading:bridge]; - } -} - -- (void)bundleFinishedLoading:(RCTBridge *)bridge -{ - RCTAssert(bridge != nil, @"Bridge cannot be nil"); - if (!bridge.valid) { - return; - } - - [_contentView removeFromSuperview]; - _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds - bridge:bridge - reactTag:self.reactTag - sizeFlexibility:_sizeFlexibility]; - [self runApplication:bridge]; - - _contentView.passThroughTouches = _passThroughTouches; - [self insertSubview:_contentView atIndex:0]; - - if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) { - self.intrinsicContentSize = self.bounds.size; - } -} - -- (void)runApplication:(RCTBridge *)bridge -{ - NSString *moduleName = _moduleName ?: @""; - NSDictionary *appParameters = @{ - @"rootTag" : _contentView.reactTag, - @"initialProps" : _appProperties ?: @{}, - }; - - RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters); - [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL]; -} - -- (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility -{ - if (_sizeFlexibility == sizeFlexibility) { - return; - } - - _sizeFlexibility = sizeFlexibility; - [self setNeedsLayout]; - _contentView.sizeFlexibility = _sizeFlexibility; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - // The root view itself should never receive touches - UIView *hitView = [super hitTest:point withEvent:event]; - if (self.passThroughTouches && hitView == self) { - return nil; - } - return hitView; -} - -- (void)setAppProperties:(NSDictionary *)appProperties -{ - RCTAssertMainQueue(); - - if ([_appProperties isEqualToDictionary:appProperties]) { - return; - } - - _appProperties = [appProperties copy]; - - if (_contentView && _bridge.valid && !_bridge.loading) { - [self runApplication:_bridge]; - } -} - -- (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize -{ - BOOL oldSizeHasAZeroDimension = _intrinsicContentSize.height == 0 || _intrinsicContentSize.width == 0; - BOOL newSizeHasAZeroDimension = intrinsicContentSize.height == 0 || intrinsicContentSize.width == 0; - BOOL bothSizesHaveAZeroDimension = oldSizeHasAZeroDimension && newSizeHasAZeroDimension; - - BOOL sizesAreEqual = CGSizeEqualToSize(_intrinsicContentSize, intrinsicContentSize); - - _intrinsicContentSize = intrinsicContentSize; - - // Don't notify the delegate if the content remains invisible or its size has not changed - if (bothSizesHaveAZeroDimension || sizesAreEqual) { - return; - } - - [self invalidateIntrinsicContentSize]; - [self.superview setNeedsLayout]; - - [_delegate rootViewDidChangeIntrinsicSize:self]; -} - -- (CGSize)intrinsicContentSize -{ - return _intrinsicContentSize; -} - -- (void)contentViewInvalidated -{ - [_contentView removeFromSuperview]; - _contentView = nil; - [self showLoadingView]; -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection -{ - [super traitCollectionDidChange:previousTraitCollection]; - if (RCTSharedApplication().applicationState == UIApplicationStateBackground) { - return; - } - - [[NSNotificationCenter defaultCenter] - postNotificationName:RCTUserInterfaceStyleDidChangeNotification - object:self - userInfo:@{ - RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey : self.traitCollection, - }]; -} - -- (void)dealloc -{ - [_contentView invalidate]; -} - -@end - -@implementation RCTRootView (Deprecated) - -- (CGSize)intrinsicSize -{ - RCTLogWarn(@"Calling deprecated `[-RCTRootView intrinsicSize]`."); - return self.intrinsicContentSize; -} - -- (void)cancelTouches -{ - RCTLogWarn(@"`-[RCTRootView cancelTouches]` is deprecated and will be deleted soon."); - [[_contentView touchHandler] cancel]; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH - @implementation RCTRootView - (nonnull instancetype)initWithFrame:(CGRect)frame bridge:(nonnull RCTBridge *)bridge @@ -426,5 +55,3 @@ - (nonnull instancetype)initWithBundleURL:(nonnull NSURL *)bundleURL } @end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTRootViewInternal.h b/packages/react-native/React/Base/RCTRootViewInternal.h index 70bafd0aa79..76b43eed58a 100644 --- a/packages/react-native/React/Base/RCTRootViewInternal.h +++ b/packages/react-native/React/Base/RCTRootViewInternal.h @@ -6,23 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -/** - * The interface provides a set of functions that allow other internal framework - * classes to change the RCTRootViews's internal state. - */ -__deprecated_msg("This API will be removed along with the legacy architecture.") @interface RCTRootView() - -/** - * This setter should be used only by RCTUIManager on react root view - * intrinsic content size update. - */ -@property (readwrite, nonatomic, assign) CGSize intrinsicContentSize; - -- (void)contentViewInvalidated; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/RCTViewRegistry.m b/packages/react-native/React/Base/RCTViewRegistry.m index ee061191b00..f03773c71b4 100644 --- a/packages/react-native/React/Base/RCTViewRegistry.m +++ b/packages/react-native/React/Base/RCTViewRegistry.m @@ -13,18 +13,8 @@ @implementation RCTViewRegistry { RCTBridgelessComponentViewProvider _bridgelessComponentViewProvider; -#ifndef RCT_REMOVE_LEGACY_ARCH - __weak RCTBridge *_bridge; -#endif // RCT_REMOVE_LEGACY_ARCH } -#ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge -{ - _bridge = bridge; -} -#endif // RCT_REMOVE_LEGACY_ARCH - - (void)setBridgelessComponentViewProvider:(RCTBridgelessComponentViewProvider)bridgelessComponentViewProvider { _bridgelessComponentViewProvider = bridgelessComponentViewProvider; @@ -34,13 +24,6 @@ - (UIView *)viewForReactTag:(NSNumber *)reactTag { UIView *view = nil; -#ifndef RCT_REMOVE_LEGACY_ARCH - RCTBridge *bridge = _bridge; - if (bridge) { - view = [bridge.uiManager viewForReactTag:reactTag]; - } -#endif // RCT_REMOVE_LEGACY_ARCH - if (view == nil && _bridgelessComponentViewProvider) { view = _bridgelessComponentViewProvider(reactTag); } @@ -55,24 +38,13 @@ - (void)addUIBlock:(RCTViewRegistryUIBlock)block } __weak __typeof(self) weakSelf = self; -#ifndef RCT_REMOVE_LEGACY_ARCH - if (_bridge) { - [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - __typeof(self) strongSelf = weakSelf; - if (strongSelf) { - block(strongSelf); - } - }]; - return; - } -#endif RCTExecuteOnMainQueue(^{ __typeof(self) strongSelf = weakSelf; if (strongSelf) { block(strongSelf); } - }); // RCT_REMOVE_LEGACY_ARCH + }); } @end diff --git a/packages/react-native/React/Base/Surface/RCTSurface.mm b/packages/react-native/React/Base/Surface/RCTSurface.mm index 6a9dd744190..8067338470c 100644 --- a/packages/react-native/React/Base/Surface/RCTSurface.mm +++ b/packages/react-native/React/Base/Surface/RCTSurface.mm @@ -27,586 +27,6 @@ #import "RCTUIManagerObserverCoordinator.h" #import "RCTUIManagerUtils.h" -#ifndef RCT_REMOVE_LEGACY_ARCH - -@interface RCTSurface () -@end - -@implementation RCTSurface { - // Immutable - RCTBridge *_bridge; - NSString *_moduleName; - NSNumber *_rootViewTag; - - // Protected by the `_mutex` - std::mutex _mutex; - RCTBridge *_batchedBridge; - RCTSurfaceStage _stage; - NSDictionary *_properties; - CGSize _minimumSize; - CGSize _maximumSize; - CGSize _intrinsicSize; - RCTUIManagerMountingBlock _mountingBlock; - - // The Main thread only - RCTSurfaceView *_Nullable _view; - RCTTouchHandler *_Nullable _touchHandler; - - // Semaphores - dispatch_semaphore_t _rootShadowViewDidStartRenderingSemaphore; - dispatch_semaphore_t _rootShadowViewDidStartLayingOutSemaphore; - dispatch_semaphore_t _uiManagerDidPerformMountingSemaphore; - - // Atomics - atomic_bool _waitingForMountingStageOnMainQueue; -} - -@synthesize delegate = _delegate; - -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties -{ - RCTAssert(bridge.valid, @"Valid bridge is required to instantiate `RCTSurface`."); - - if (self = [super init]) { - _bridge = bridge; - _batchedBridge = [_bridge batchedBridge] ?: _bridge; - _moduleName = moduleName; - _properties = [initialProperties copy]; - _rootViewTag = RCTAllocateRootViewTag(); - - _rootShadowViewDidStartRenderingSemaphore = dispatch_semaphore_create(0); - _rootShadowViewDidStartLayingOutSemaphore = dispatch_semaphore_create(0); - _uiManagerDidPerformMountingSemaphore = dispatch_semaphore_create(0); - - _minimumSize = CGSizeZero; - _maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleBridgeWillLoadJavaScriptNotification:) - name:RCTJavaScriptWillStartLoadingNotification - object:_bridge]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleBridgeDidLoadJavaScriptNotification:) - name:RCTJavaScriptDidLoadNotification - object:_bridge]; - - _stage = RCTSurfaceStageSurfaceDidInitialize; - - if (!bridge.loading) { - _stage = _stage | RCTSurfaceStageBridgeDidLoad; - } - - [_bridge.uiManager.observerCoordinator addObserver:self]; - - [self _registerRootView]; - [self _run]; - } - - return self; -} - -- (void)dealloc -{ - [self _stop]; -} - -#pragma mark - Immutable Properties (no need to enforce synchronization) - -- (RCTBridge *)bridge -{ - return _bridge; -} - -- (NSString *)moduleName -{ - return _moduleName; -} - -- (NSNumber *)rootViewTag -{ - return _rootViewTag; -} - -#pragma mark - Convenience Internal Thread-Safe Properties - -- (RCTBridge *)_batchedBridge -{ - std::lock_guard lock(_mutex); - return _batchedBridge; -} - -- (RCTUIManager *)_uiManager -{ - return self._batchedBridge.uiManager; -} - -#pragma mark - Main-Threaded Routines - -- (RCTSurfaceView *)view -{ - RCTAssertMainQueue(); - - if (!_view) { - _view = [[RCTSurfaceView alloc] initWithSurface:self]; - - _touchHandler = [[RCTTouchHandler alloc] initWithBridge:self.bridge]; - [_touchHandler attachToView:_view]; - - [self _mountRootViewIfNeeded]; - } - - return _view; -} - -- (void)_mountRootViewIfNeeded -{ - RCTAssertMainQueue(); - - RCTSurfaceView *view = self->_view; - if (!view) { - return; - } - - RCTSurfaceRootView *rootView = (RCTSurfaceRootView *)[self._uiManager viewForReactTag:self->_rootViewTag]; - if (!rootView) { - return; - } - - RCTAssert( - [rootView isKindOfClass:[RCTSurfaceRootView class]], - @"Received root view is not an instance of `RCTSurfaceRootView`."); - - if (rootView.superview != view) { - view.rootView = rootView; - } -} - -#pragma mark - Bridge Events - -- (void)handleBridgeWillLoadJavaScriptNotification:(__unused NSNotification *)notification -{ - RCTAssertMainQueue(); - - // Reset states because the bridge is reloading. This is similar to initialization phase. - _stage = RCTSurfaceStageSurfaceDidInitialize; - _view = nil; - _touchHandler = nil; - [self _setStage:RCTSurfaceStageBridgeDidLoad]; -} - -- (void)handleBridgeDidLoadJavaScriptNotification:(NSNotification *)notification -{ - RCTAssertMainQueue(); - - [self _setStage:RCTSurfaceStageModuleDidLoad]; - - RCTBridge *bridge = notification.userInfo[@"bridge"]; - - BOOL isRerunNeeded = NO; - - { - std::lock_guard lock(_mutex); - - if (bridge != _batchedBridge) { - _batchedBridge = bridge; - isRerunNeeded = YES; - } - } - - if (isRerunNeeded) { - [self _registerRootView]; - [self _run]; - } -} - -#pragma mark - Stage management - -- (RCTSurfaceStage)stage -{ - std::lock_guard lock(_mutex); - return _stage; -} - -- (void)_setStage:(RCTSurfaceStage)stage -{ - RCTSurfaceStage updatedStage; - { - std::lock_guard lock(_mutex); - - if (_stage & stage) { - return; - } - - updatedStage = (RCTSurfaceStage)(_stage | stage); - _stage = updatedStage; - } - - [self _propagateStageChange:updatedStage]; -} - -- (void)_propagateStageChange:(RCTSurfaceStage)stage -{ - // Updating the `view` - RCTExecuteOnMainQueue(^{ - self->_view.stage = stage; - }); - - // Notifying the `delegate` - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(surface:didChangeStage:)]) { - [delegate surface:self didChangeStage:stage]; - } -} - -#pragma mark - Properties Management - -- (NSDictionary *)properties -{ - std::lock_guard lock(_mutex); - return _properties; -} - -- (void)setProperties:(NSDictionary *)properties -{ - { - std::lock_guard lock(_mutex); - - if ([properties isEqualToDictionary:_properties]) { - return; - } - - _properties = [properties copy]; - } - - [self _run]; -} - -#pragma mark - Running - -- (void)_run -{ - RCTBridge *batchedBridge; - NSDictionary *properties; - - { - std::lock_guard lock(_mutex); - - batchedBridge = _batchedBridge; - properties = _properties; - } - - if (!batchedBridge.valid) { - return; - } - - NSDictionary *applicationParameters = @{ - @"rootTag" : _rootViewTag, - @"initialProps" : properties, - }; - - RCTLogInfo(@"Running surface %@ (%@)", _moduleName, applicationParameters); - - [self mountReactComponentWithBridge:batchedBridge moduleName:_moduleName params:applicationParameters]; - - [self _setStage:RCTSurfaceStageSurfaceDidRun]; -} - -- (void)_stop -{ - [self unmountReactComponentWithBridge:self._batchedBridge rootViewTag:self->_rootViewTag]; -} - -- (void)_registerRootView -{ - RCTBridge *batchedBridge; - CGSize minimumSize; - CGSize maximumSize; - - { - std::lock_guard lock(_mutex); - batchedBridge = _batchedBridge; - minimumSize = _minimumSize; - maximumSize = _maximumSize; - } - - RCTUIManager *uiManager = batchedBridge.uiManager; - - // If we are on the main queue now, we have to proceed synchronously. - // Otherwise, we cannot perform synchronous waiting for some stages later. - (RCTIsMainQueue() ? RCTUnsafeExecuteOnUIManagerQueueSync : RCTExecuteOnUIManagerQueue)(^{ - [uiManager registerRootViewTag:self->_rootViewTag]; - - RCTSurfaceRootShadowView *rootShadowView = - (RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag]; - RCTAssert( - [rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]], - @"Received shadow view is not an instance of `RCTSurfaceRootShadowView`."); - - [rootShadowView setMinimumSize:minimumSize maximumSize:maximumSize]; - rootShadowView.delegate = self; - }); -} - -#pragma mark - Layout - -- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize -{ - RCTUIManager *uiManager = self._uiManager; - __block CGSize fittingSize; - - RCTUnsafeExecuteOnUIManagerQueueSync(^{ - RCTSurfaceRootShadowView *rootShadowView = - (RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag]; - - RCTAssert( - [rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]], - @"Received shadow view is not an instance of `RCTSurfaceRootShadowView`."); - - fittingSize = [rootShadowView sizeThatFitsMinimumSize:minimumSize maximumSize:maximumSize]; - }); - - return fittingSize; -} - -#pragma mark - Size Constraints - -- (void)setSize:(CGSize)size -{ - // `viewportOffset` is intentionally zero because `RCTSurface` ignores it. - // However, it is needed in `RCTFabricSurface`. - [self setMinimumSize:size maximumSize:size viewportOffset:CGPointZero]; -} - -- (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize viewportOffset:(CGPoint)viewportOffset -{ - { - std::lock_guard lock(_mutex); - if (CGSizeEqualToSize(minimumSize, _minimumSize) && CGSizeEqualToSize(maximumSize, _maximumSize)) { - return; - } - - _maximumSize = maximumSize; - _minimumSize = minimumSize; - } - - RCTUIManager *uiManager = self._uiManager; - - RCTUnsafeExecuteOnUIManagerQueueSync(^{ - RCTSurfaceRootShadowView *rootShadowView = - (RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag]; - RCTAssert( - [rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]], - @"Received shadow view is not an instance of `RCTSurfaceRootShadowView`."); - - [rootShadowView setMinimumSize:minimumSize maximumSize:maximumSize]; - [uiManager setNeedsLayout]; - }); -} - -- (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize -{ - [self setMinimumSize:minimumSize maximumSize:maximumSize viewportOffset:CGPointZero]; -} - -- (CGSize)minimumSize -{ - std::lock_guard lock(_mutex); - return _minimumSize; -} - -- (CGSize)maximumSize -{ - std::lock_guard lock(_mutex); - return _maximumSize; -} - -#pragma mark - intrinsicSize - -- (void)setIntrinsicSize:(CGSize)intrinsicSize -{ - { - std::lock_guard lock(_mutex); - if (CGSizeEqualToSize(intrinsicSize, _intrinsicSize)) { - return; - } - - _intrinsicSize = intrinsicSize; - } - - // Notifying `delegate` - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(surface:didChangeIntrinsicSize:)]) { - [delegate surface:self didChangeIntrinsicSize:intrinsicSize]; - } -} - -- (CGSize)intrinsicSize -{ - std::lock_guard lock(_mutex); - return _intrinsicSize; -} - -#pragma mark - Synchronous Waiting - -- (BOOL)synchronouslyWaitForStage:(RCTSurfaceStage)stage timeout:(NSTimeInterval)timeout -{ - if (RCTIsUIManagerQueue()) { - RCTLogInfo(@"Synchronous waiting is not supported on UIManager queue."); - return NO; - } - - if (RCTIsMainQueue() && (stage & RCTSurfaceStageSurfaceDidInitialMounting)) { - // All main-threaded execution (especially mounting process) has to be - // intercepted, captured and performed synchronously at the end of this method - // right after the semaphore signals. - - // Atomic variant of `_waitingForMountingStageOnMainQueue = YES;` - atomic_fetch_or(&_waitingForMountingStageOnMainQueue, 1); - } - - dispatch_semaphore_t semaphore; - switch (stage) { - case RCTSurfaceStageSurfaceDidInitialLayout: - semaphore = _rootShadowViewDidStartLayingOutSemaphore; - break; - case RCTSurfaceStageSurfaceDidInitialRendering: - semaphore = _rootShadowViewDidStartRenderingSemaphore; - break; - case RCTSurfaceStageSurfaceDidInitialMounting: - semaphore = _uiManagerDidPerformMountingSemaphore; - break; - default: - RCTAssert( - NO, - @"Only waiting for `RCTSurfaceStageSurfaceDidInitialRendering`, `RCTSurfaceStageSurfaceDidInitialLayout` and `RCTSurfaceStageSurfaceDidInitialMounting` stages are supported."); - } - - auto timeoutOccurred = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC)); - - // Atomic equivalent of `_waitingForMountingStageOnMainQueue = NO;`. - atomic_fetch_and(&_waitingForMountingStageOnMainQueue, 0); - - if (!timeoutOccurred) { - // Balancing the semaphore. - // Note: `dispatch_semaphore_wait` reverts the decrement in case when timeout occurred. - dispatch_semaphore_signal(semaphore); - } - - if (RCTIsMainQueue() && (stage & RCTSurfaceStageSurfaceDidInitialMounting)) { - // Time to apply captured mounting block. - RCTUIManagerMountingBlock mountingBlock; - { - std::lock_guard lock(_mutex); - mountingBlock = _mountingBlock; - _mountingBlock = nil; - } - - if (mountingBlock) { - mountingBlock(); - [self _mountRootViewIfNeeded]; - } - } - - return !timeoutOccurred; -} - -#pragma mark - RCTSurfaceRootShadowViewDelegate - -- (void)rootShadowView:(__unused RCTRootShadowView *)rootShadowView didChangeIntrinsicSize:(CGSize)intrinsicSize -{ - self.intrinsicSize = intrinsicSize; -} - -- (void)rootShadowViewDidStartRendering:(__unused RCTSurfaceRootShadowView *)rootShadowView -{ - [self _setStage:RCTSurfaceStageSurfaceDidInitialRendering]; - - dispatch_semaphore_signal(_rootShadowViewDidStartRenderingSemaphore); -} - -- (void)rootShadowViewDidStartLayingOut:(__unused RCTSurfaceRootShadowView *)rootShadowView -{ - [self _setStage:RCTSurfaceStageSurfaceDidInitialLayout]; - - dispatch_semaphore_signal(_rootShadowViewDidStartLayingOutSemaphore); - - RCTExecuteOnMainQueue(^{ - // Rendering is happening, let's mount `rootView` into `view` if we already didn't do this. - [self _mountRootViewIfNeeded]; - }); -} - -#pragma mark - RCTUIManagerObserver - -- (BOOL)uiManager:(__unused RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block -{ - if (atomic_load(&_waitingForMountingStageOnMainQueue) && (self.stage & RCTSurfaceStageSurfaceDidInitialLayout)) { - // Atomic equivalent of `_waitingForMountingStageOnMainQueue = NO;`. - atomic_fetch_and(&_waitingForMountingStageOnMainQueue, 0); - - { - std::lock_guard lock(_mutex); - _mountingBlock = block; - } - return YES; - } - - return NO; -} - -- (void)uiManagerDidPerformMounting:(__unused RCTUIManager *)manager -{ - if (self.stage & RCTSurfaceStageSurfaceDidInitialLayout) { - [self _setStage:RCTSurfaceStageSurfaceDidInitialMounting]; - dispatch_semaphore_signal(_uiManagerDidPerformMountingSemaphore); - - // No need to listen to UIManager anymore. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ - [self->_bridge.uiManager.observerCoordinator removeObserver:self]; - }); - } -} - -- (void)start -{ - // Does nothing. - // The Start&Stop feature is not implemented for regular Surface yet. -} - -- (void)stop -{ - // Does nothing. - // The Start&Stop feature is not implemented for regular Surface yet. -} - -#pragma mark - Mounting/Unmounting of React components - -- (void)mountReactComponentWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - params:(NSDictionary *)params -{ - [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, params ] completion:NULL]; -} - -- (void)unmountReactComponentWithBridge:(RCTBridge *)bridge rootViewTag:(NSNumber *)rootViewTag -{ - [bridge enqueueJSCall:@"AppRegistry" - method:@"unmountApplicationComponentAtRootTag" - args:@[ rootViewTag ] - completion:NULL]; -} - -- (NSInteger)rootTag -{ - return _rootViewTag.integerValue; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH @implementation RCTSurface @synthesize stage; @synthesize moduleName; @@ -669,5 +89,3 @@ - (void)stop } @end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Base/Surface/RCTSurfaceRootShadowView.m b/packages/react-native/React/Base/Surface/RCTSurfaceRootShadowView.m index c87c3f73d74..8f3c0518aa5 100644 --- a/packages/react-native/React/Base/Surface/RCTSurfaceRootShadowView.m +++ b/packages/react-native/React/Base/Surface/RCTSurfaceRootShadowView.m @@ -7,93 +7,6 @@ #import "RCTSurfaceRootShadowView.h" -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTI18nUtil.h" -#import "RCTShadowView+Layout.h" -#import "RCTUIManagerUtils.h" - -@implementation RCTSurfaceRootShadowView { - CGSize _intrinsicSize; - BOOL _isRendered; - BOOL _isLaidOut; -} - -- (instancetype)init -{ - if (self = [super init]) { - self.viewName = @"RCTSurfaceRootView"; - _baseDirection = [[RCTI18nUtil sharedInstance] isRTL] ? YGDirectionRTL : YGDirectionLTR; - _minimumSize = CGSizeZero; - _maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); - - self.alignSelf = YGAlignStretch; - self.flex = 1; - } - - return self; -} - -- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:subview atIndex:atIndex]; - if (!_isRendered) { - [_delegate rootShadowViewDidStartRendering:self]; - _isRendered = YES; - } -} - -- (void)layoutWithAffectedShadowViews:(NSPointerArray *)affectedShadowViews -{ - NSHashTable *other = [NSHashTable new]; - - RCTLayoutContext layoutContext = {}; - layoutContext.affectedShadowViews = affectedShadowViews; - layoutContext.other = other; - - [self layoutWithMinimumSize:_minimumSize - maximumSize:_maximumSize - layoutDirection:RCTUIKitLayoutDirectionFromYogaLayoutDirection(_baseDirection) - layoutContext:layoutContext]; - - self.intrinsicSize = self.layoutMetrics.frame.size; - - if (_isRendered && !_isLaidOut) { - [_delegate rootShadowViewDidStartLayingOut:self]; - _isLaidOut = YES; - } -} - -- (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize -{ - if (CGSizeEqualToSize(minimumSize, _minimumSize) && CGSizeEqualToSize(maximumSize, _maximumSize)) { - return; - } - - _maximumSize = maximumSize; - _minimumSize = minimumSize; -} - -- (void)setIntrinsicSize:(CGSize)intrinsicSize -{ - if (CGSizeEqualToSize(_intrinsicSize, intrinsicSize)) { - return; - } - - _intrinsicSize = intrinsicSize; - - [_delegate rootShadowView:self didChangeIntrinsicSize:intrinsicSize]; -} - -- (CGSize)intrinsicSize -{ - return _intrinsicSize; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH - @implementation RCTSurfaceRootShadowView - (void)setMinimumSize:(CGSize)size maximumSize:(CGSize)maximumSize { @@ -104,5 +17,3 @@ - (void)layoutWithAffectedShadowViews:(NSPointerArray *)affectedShadowViews } @end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CoreModules/RCTLogBox.mm b/packages/react-native/React/CoreModules/RCTLogBox.mm index adfbb11a80e..d9663afb29c 100644 --- a/packages/react-native/React/CoreModules/RCTLogBox.mm +++ b/packages/react-native/React/CoreModules/RCTLogBox.mm @@ -59,17 +59,6 @@ - (void)setSurfacePresenter:(id)surfacePresenter surfacePresenter:strongSelf->_bridgelessSurfacePresenter]; [strongSelf->_view show]; } -#ifndef RCT_REMOVE_LEGACY_ARCH - else if ((strongSelf->_bridge != nullptr) && strongSelf->_bridge.valid) { - if (strongSelf->_bridge.surfacePresenter != nullptr) { - strongSelf->_view = [[RCTLogBoxView alloc] initWithWindow:RCTKeyWindow() - surfacePresenter:strongSelf->_bridge.surfacePresenter]; - } else { - strongSelf->_view = [[RCTLogBoxView alloc] initWithWindow:RCTKeyWindow() bridge:strongSelf->_bridge]; - } - [strongSelf->_view show]; - } -#endif // RCT_REMOVE_LEGACY_ARCH }); } } diff --git a/packages/react-native/React/CoreModules/RCTLogBoxView.h b/packages/react-native/React/CoreModules/RCTLogBoxView.h index 00ccfdf40b2..56d0d7ba84e 100644 --- a/packages/react-native/React/CoreModules/RCTLogBoxView.h +++ b/packages/react-native/React/CoreModules/RCTLogBoxView.h @@ -16,9 +16,6 @@ - (void)createRootViewController:(UIView *)view; -#ifndef RCT_REMOVE_LEGACY_ARCH -- (instancetype)initWithWindow:(UIWindow *)window bridge:(RCTBridge *)bridge; -#endif // RCT_REMOVE_LEGACY_ARCH - (instancetype)initWithWindow:(UIWindow *)window surfacePresenter:(id)surfacePresenter; - (void)show; diff --git a/packages/react-native/React/CoreModules/RCTLogBoxView.mm b/packages/react-native/React/CoreModules/RCTLogBoxView.mm index 79f9aa288a4..0bd2b6653c8 100644 --- a/packages/react-native/React/CoreModules/RCTLogBoxView.mm +++ b/packages/react-native/React/CoreModules/RCTLogBoxView.mm @@ -12,9 +12,6 @@ #import @implementation RCTLogBoxView { -#ifndef RCT_REMOVE_LEGACY_ARCH - RCTSurface *_surface; -#endif // RCT_REMOVE_LEGACY_ARCH } - (instancetype)initWithFrame:(CGRect)frame @@ -37,28 +34,6 @@ - (void)createRootViewController:(UIView *)view self.rootViewController = _rootViewController; } -#ifndef RCT_REMOVE_LEGACY_ARCH -- (instancetype)initWithWindow:(UIWindow *)window bridge:(RCTBridge *)bridge -{ - self = [super initWithWindowScene:window.windowScene]; - -#if !TARGET_OS_TV - self.windowLevel = UIWindowLevelStatusBar - 1; -#endif - self.backgroundColor = [UIColor clearColor]; - - _surface = [[RCTSurface alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:@{}]; - [_surface start]; - - if (![_surface synchronouslyWaitForStage:RCTSurfaceStageSurfaceDidInitialMounting timeout:1]) { - RCTLogInfo(@"Failed to mount LogBox within 1s"); - } - [self createRootViewController:(UIView *)_surface.view]; - - return self; -} -#endif // RCT_REMOVE_LEGACY_ARCH - - (instancetype)initWithWindow:(UIWindow *)window surfacePresenter:(id)surfacePresenter { self = [super initWithWindowScene:window.windowScene]; @@ -76,9 +51,6 @@ - (instancetype)initWithWindow:(UIWindow *)window surfacePresenter:(id #endif -#ifndef RCT_REMOVE_LEGACY_ARCH - -static NSString *const RCTJSThreadName = @"com.facebook.react.JavaScript"; - -using RCTPendingCall = void (^)(); - -using namespace facebook::jsi; -using namespace facebook::react; - -/** - * Must be kept in sync with `MessageQueue.js`. - */ -typedef NS_ENUM(NSInteger, RCTBridgeFields) { - RCTBridgeFieldRequestModuleIDs = 0, - RCTBridgeFieldMethodIDs, - RCTBridgeFieldParams, - RCTBridgeFieldCallID, -}; - -namespace { - -int32_t getUniqueId() -{ - static std::atomic counter{0}; - return counter++; -} - -class GetDescAdapter : public JSExecutorFactory { - public: - GetDescAdapter(RCTCxxBridge *bridge, std::shared_ptr factory) : bridge_(bridge), factory_(factory) - { - } - std::unique_ptr createJSExecutor( - std::shared_ptr delegate, - std::shared_ptr jsQueue) override - { - auto ret = factory_->createJSExecutor(delegate, jsQueue); - bridge_.bridgeDescription = @(ret->getDescription().c_str()); - return ret; - } - - private: - RCTCxxBridge *bridge_; - std::shared_ptr factory_; -}; - -} // namespace - -static void mapReactMarkerToPerformanceLogger( - const ReactMarker::ReactMarkerId markerId, - RCTPerformanceLogger *performanceLogger, - const char *tag) -{ - switch (markerId) { - case ReactMarker::APP_STARTUP_START: - [performanceLogger markStartForTag:RCTPLAppStartup]; - break; - case ReactMarker::APP_STARTUP_STOP: - [performanceLogger markStopForTag:RCTPLAppStartup]; - break; - case ReactMarker::INIT_REACT_RUNTIME_START: - [performanceLogger markStartForTag:RCTPLInitReactRuntime]; - break; - case ReactMarker::INIT_REACT_RUNTIME_STOP: - [performanceLogger markStopForTag:RCTPLInitReactRuntime]; - break; - case ReactMarker::RUN_JS_BUNDLE_START: - [performanceLogger markStartForTag:RCTPLScriptExecution]; - break; - case ReactMarker::RUN_JS_BUNDLE_STOP: - [performanceLogger markStopForTag:RCTPLScriptExecution]; - break; - case ReactMarker::NATIVE_REQUIRE_START: - [performanceLogger appendStartForTag:RCTPLRAMNativeRequires]; - break; - case ReactMarker::NATIVE_REQUIRE_STOP: - [performanceLogger appendStopForTag:RCTPLRAMNativeRequires]; - [performanceLogger addValue:1 forTag:RCTPLRAMNativeRequiresCount]; - break; - case ReactMarker::NATIVE_MODULE_SETUP_START: - [performanceLogger markStartForTag:RCTPLNativeModuleSetup]; - break; - case ReactMarker::NATIVE_MODULE_SETUP_STOP: - [performanceLogger markStopForTag:RCTPLNativeModuleSetup]; - break; - // Not needed in bridge mode. - case ReactMarker::REACT_INSTANCE_INIT_START: - case ReactMarker::REACT_INSTANCE_INIT_STOP: - // Not used on iOS. - case ReactMarker::CREATE_REACT_CONTEXT_STOP: - case ReactMarker::JS_BUNDLE_STRING_CONVERT_START: - case ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP: - case ReactMarker::REGISTER_JS_SEGMENT_START: - case ReactMarker::REGISTER_JS_SEGMENT_STOP: - break; - } -} - -static void registerPerformanceLoggerHooks(RCTPerformanceLogger *performanceLogger) -{ - std::unique_lock lock(ReactMarker::logTaggedMarkerImplMutex); - __weak RCTPerformanceLogger *weakPerformanceLogger = performanceLogger; - ReactMarker::LogTaggedMarker newMarker = [weakPerformanceLogger]( - const ReactMarker::ReactMarkerId markerId, const char *tag) { - mapReactMarkerToPerformanceLogger(markerId, weakPerformanceLogger, tag); - }; - ReactMarker::logTaggedMarkerImpl = newMarker; -} - -@interface RCTCxxBridge () - -@property (nonatomic, weak, readonly) RCTBridge *parentBridge; -@property (nonatomic, assign, readonly) BOOL moduleSetupComplete; - -- (instancetype)initWithParentBridge:(RCTBridge *)bridge; -- (void)batchDidComplete; - -@end - -struct RCTInstanceCallback : public InstanceCallback { - __weak RCTCxxBridge *bridge_; - RCTInstanceCallback(RCTCxxBridge *bridge) : bridge_(bridge) {}; - void onBatchComplete() override - { - [bridge_ batchDidComplete]; - } -}; - -@interface RCTBridgeDisplayLinkModuleHolder : NSObject -- (instancetype)initWithModuleData:(RCTModuleData *)moduleData; -@end - -@implementation RCTBridgeDisplayLinkModuleHolder { - RCTModuleData *_moduleData; -} - -- (instancetype)initWithModuleData:(RCTModuleData *)moduleData -{ - _moduleData = moduleData; - return self; -} - -- (id)instance -{ - return _moduleData.instance; -} - -- (Class)moduleClass -{ - return _moduleData.moduleClass; -} - -- (dispatch_queue_t)methodQueue -{ - return _moduleData.methodQueue; -} - -@end - -@implementation RCTCxxBridge { - BOOL _didInvalidate; - std::atomic _moduleRegistryCreated; - - NSMutableArray *_pendingCalls; - std::atomic _pendingCount; - - // Native modules - NSMutableDictionary *_moduleDataByName; - NSMutableArray *_moduleDataByID; - NSMutableArray *_moduleClassesByID; - NSUInteger _modulesInitializedOnMainQueue; - RCTDisplayLink *_displayLink; - - // JS thread management - NSThread *_jsThread; - std::shared_ptr _jsMessageThread; - std::mutex _moduleRegistryLock; - - // This is uniquely owned, but weak_ptr is used. - std::shared_ptr _reactInstance; - - // Necessary for searching in TurboModules in TurboModuleManager - id _turboModuleRegistry; - - RCTModuleRegistry *_objCModuleRegistry; - RCTViewRegistry *_viewRegistry_DEPRECATED; - RCTBundleManager *_bundleManager; - RCTCallableJSModules *_callableJSModules; - std::atomic _loading; - std::atomic _valid; -} - -@synthesize bridgeDescription = _bridgeDescription; -@synthesize performanceLogger = _performanceLogger; - -- (BOOL)isLoading -{ - return _loading; -} - -- (void)setLoading:(BOOL)newValue -{ - _loading = newValue; -} - -- (BOOL)isValid -{ - return _valid; -} - -- (void)setValid:(BOOL)newValue -{ - _valid = newValue; -} - -- (RCTModuleRegistry *)moduleRegistry -{ - return _objCModuleRegistry; -} - -- (void)setRCTTurboModuleRegistry:(id)turboModuleRegistry -{ - _turboModuleRegistry = turboModuleRegistry; - [_objCModuleRegistry setTurboModuleRegistry:_turboModuleRegistry]; -} - -- (RCTBridgeModuleDecorator *)bridgeModuleDecorator -{ - return [[RCTBridgeModuleDecorator alloc] initWithViewRegistry:_viewRegistry_DEPRECATED - moduleRegistry:_objCModuleRegistry - bundleManager:_bundleManager - callableJSModules:_callableJSModules]; -} - -- (std::shared_ptr)jsMessageThread -{ - return _jsMessageThread; -} - -- (BOOL)isInspectable -{ - return _reactInstance ? _reactInstance->isInspectable() : NO; -} - -- (instancetype)initWithParentBridge:(RCTBridge *)bridge -{ - RCTAssertParam(bridge); - - if ((self = [super initWithDelegate:bridge.delegate - bundleURL:bridge.bundleURL - moduleProvider:bridge.moduleProvider - launchOptions:bridge.launchOptions])) { - _parentBridge = bridge; - _performanceLogger = [bridge performanceLogger]; - - registerPerformanceLoggerHooks(_performanceLogger); - - /** - * Set Initial State - */ - _valid = YES; - _loading = YES; - _moduleRegistryCreated = NO; - _pendingCalls = [NSMutableArray new]; - _displayLink = [RCTDisplayLink new]; - _moduleDataByName = [NSMutableDictionary new]; - _moduleClassesByID = [NSMutableArray new]; - _moduleDataByID = [NSMutableArray new]; - _objCModuleRegistry = [RCTModuleRegistry new]; - [_objCModuleRegistry setBridge:self]; - _bundleManager = [RCTBundleManager new]; - [_bundleManager setBridge:self]; - _viewRegistry_DEPRECATED = [RCTViewRegistry new]; - [_viewRegistry_DEPRECATED setBridge:self]; - _callableJSModules = [RCTCallableJSModules new]; - [_callableJSModules setBridge:self]; - - [RCTBridge setCurrentBridge:self]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleMemoryWarning) - name:UIApplicationDidReceiveMemoryWarningNotification - object:nil]; - - RCTLogSetBridgeModuleRegistry(_objCModuleRegistry); - RCTLogSetBridgeCallableJSModules(_callableJSModules); - } - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -+ (void)runRunLoop -{ - @autoreleasepool { - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge runJSRunLoop] setup", nil); - - // copy thread name to pthread name - pthread_setname_np([NSThread currentThread].name.UTF8String); - - // Set up a dummy runloop source to avoid spinning - CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; - CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx); - CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode); - CFRelease(noSpinSource); - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - // run the run loop - while (kCFRunLoopRunStopped != - CFRunLoopRunInMode( - kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) { - RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad. - } - } -} - -- (void)_tryAndHandleError:(dispatch_block_t)block -{ - NSError *error = tryAndReturnError(block); - if (error) { - [self handleError:error]; - } -} - -- (void)handleMemoryWarning -{ - // We only want to run garbage collector when the loading is finished - // and the instance is valid. - if (!_valid || _loading) { - return; - } - - // We need to hold a local retaining pointer to react instance - // in case if some other tread resets it. - auto reactInstance = _reactInstance; - if (reactInstance) { - // Memory Pressure Unloading Level 15 represents TRIM_MEMORY_RUNNING_CRITICAL. - int unloadLevel = 15; - reactInstance->handleMemoryPressure(unloadLevel); - } -} - -/** - * Ensure block is run on the JS thread. If we're already on the JS thread, the block will execute synchronously. - * If we're not on the JS thread, the block is dispatched to that thread. Any errors encountered while executing - * the block will go through handleError: - */ -- (void)ensureOnJavaScriptThread:(dispatch_block_t)block -{ - RCTAssert(_jsThread, @"This method must not be called before the JS thread is created"); - - // This does not use _jsMessageThread because it may be called early before the runloop reference is captured - // and _jsMessageThread is valid. _jsMessageThread also doesn't allow us to shortcut the dispatch if we're - // already on the correct thread. - - if ([NSThread currentThread] == _jsThread) { - [self _tryAndHandleError:block]; - } else { - [self performSelector:@selector(_tryAndHandleError:) onThread:_jsThread withObject:block waitUntilDone:NO]; - } -} - -- (void)start -{ - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge start]", nil); - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification - object:_parentBridge - userInfo:@{@"bridge" : self}]; - - // Set up the JS thread early - _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil]; - _jsThread.name = RCTJSThreadName; - _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive; -#if RCT_DEBUG - _jsThread.stackSize *= 2; -#endif - [_jsThread start]; - - dispatch_group_t prepareBridge = dispatch_group_create(); - - [_performanceLogger markStartForTag:RCTPLNativeModuleInit]; - - [self registerExtraModules]; - // Initialize all native modules that cannot be loaded lazily - (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]; - - [_performanceLogger markStopForTag:RCTPLNativeModuleInit]; - - // This doesn't really do anything. The real work happens in initializeBridge. - _reactInstance.reset(new Instance); - - __weak RCTCxxBridge *weakSelf = self; - - // Prepare executor factory (shared_ptr for copy into block) - std::shared_ptr executorFactory; - if (!self.executorClass) { - if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) { - id cxxDelegate = (id)self.delegate; - executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self]; - } - if (!executorFactory) { - auto installBindings = RCTJSIExecutorRuntimeInstaller(nullptr); -#if !defined(USE_HERMES) || USE_HERMES == 1 - executorFactory = std::make_shared(installBindings); -#endif - } - } else { - id objcExecutor = [self moduleForClass:self.executorClass]; - executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) { - if (error) { - [weakSelf handleError:error]; - } - })); - } - - // Grab the inspector target from the parent bridge here, on the main queue, to avoid inadvertently - // moving ownership of the parent bridge to the JS thread, which would violate expectations around - // the timing and thread affinity of RCTBridge's destruction. This could technically be a dangling - // pointer into a member of RCTBridge! But we only use it while _reactInstance exists, meaning we - // haven't been invalidated, and therefore RCTBridge hasn't been deallocated yet. - RCTAssertMainQueue(); - facebook::react::jsinspector_modern::HostTarget *parentInspectorTarget = _parentBridge.inspectorTarget; - - // Dispatch the instance initialization as soon as the initial module metadata has - // been collected (see initModules) - dispatch_group_enter(prepareBridge); - [self ensureOnJavaScriptThread:^{ - [weakSelf _initializeBridge:executorFactory parentInspectorTarget:parentInspectorTarget]; - dispatch_group_leave(prepareBridge); - }]; - - // Load the source asynchronously, then store it for later execution. - dispatch_group_enter(prepareBridge); - __block NSData *sourceCode; - __block NSURL *sourceURL = self.bundleURL; - -#if RCT_DEV_MENU && __has_include() - { - id loadingView = [self moduleForName:@"DevLoadingView" lazilyLoadIfNecessary:YES]; - [loadingView showWithURL:self.bundleURL]; - } -#endif - - [self - loadSource:^(NSError *error, RCTSource *source) { - if (error) { - [weakSelf handleError:error]; - } - - sourceCode = source.data; - if (source.url) { - sourceURL = source.url; - } - dispatch_group_leave(prepareBridge); - } - onProgress:^(RCTLoadingProgress *progressData) { -#if RCT_DEV_MENU && __has_include() - id loadingView = [weakSelf moduleForName:@"DevLoadingView" - lazilyLoadIfNecessary:YES]; - [loadingView updateProgress:progressData]; -#endif - }]; - - // Wait for both the modules and source code to have finished loading - dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ - RCTCxxBridge *strongSelf = weakSelf; - if (sourceCode && strongSelf.loading) { - [strongSelf executeSourceCode:sourceCode withSourceURL:sourceURL sync:NO]; - } - }); - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center postNotificationName:RCTBridgeWillDownloadScriptNotification object:_parentBridge]; - [_performanceLogger markStartForTag:RCTPLScriptDownload]; - NSUInteger cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil); - - // Suppress a warning if RCTProfileBeginAsyncEvent gets compiled out - (void)cookie; - - RCTPerformanceLogger *performanceLogger = _performanceLogger; - RCTSourceLoadBlock onSourceLoad = ^(NSError *error, RCTSource *source) { - RCTProfileEndAsyncEvent(0, @"native", cookie, @"JavaScript download", @"JS async"); - [performanceLogger markStopForTag:RCTPLScriptDownload]; - [performanceLogger setValue:source.length forTag:RCTPLBundleSize]; - - NSDictionary *userInfo = @{ - RCTBridgeDidDownloadScriptNotificationSourceKey : source ?: [NSNull null], - RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey : self->_bridgeDescription ?: [NSNull null], - }; - - [center postNotificationName:RCTBridgeDidDownloadScriptNotification object:self->_parentBridge userInfo:userInfo]; - - _onSourceLoad(error, source); - }; - - if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) { - [self.delegate loadSourceForBridge:_parentBridge onProgress:onProgress onComplete:onSourceLoad]; - } else if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) { - [self.delegate loadSourceForBridge:_parentBridge withBlock:onSourceLoad]; - } else if (!self.bundleURL) { - NSError *error = RCTErrorWithMessage( - @"No bundle URL present.\n\nMake sure you're running a packager " - "server or have included a .jsbundle file in your application bundle."); - onSourceLoad(error, nil); - } else { - __weak RCTCxxBridge *weakSelf = self; - [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL - onProgress:onProgress - onComplete:^(NSError *error, RCTSource *source) { - if (error) { - [weakSelf handleError:error]; - return; - } - onSourceLoad(error, source); - }]; - } -} - -- (NSArray *)moduleClasses -{ - if (RCT_DEBUG && _valid && _moduleClassesByID == nil) { - RCTLogError( - @"Bridge modules have not yet been initialized. You may be " - "trying to access a module too early in the startup procedure."); - } - return _moduleClassesByID; -} - -/** - * Used by RCTUIManager - */ -- (RCTModuleData *)moduleDataForName:(NSString *)moduleName -{ - return _moduleDataByName[moduleName]; -} - -- (id)moduleForName:(NSString *)moduleName -{ - return [self moduleForName:moduleName lazilyLoadIfNecessary:NO]; -} - -- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad -{ - if (RCTTurboModuleEnabled() && _turboModuleRegistry) { - const char *moduleNameCStr = [moduleName UTF8String]; - if (lazilyLoad || [_turboModuleRegistry moduleIsInitialized:moduleNameCStr]) { - id module = [_turboModuleRegistry moduleForName:moduleNameCStr warnOnLookupFailure:NO]; - if (module != nil) { - return module; - } - } - } - - if (!lazilyLoad) { - return _moduleDataByName[moduleName].instance; - } - - RCTModuleData *moduleData = _moduleDataByName[moduleName]; - if (moduleData) { - if (![moduleData isKindOfClass:[RCTModuleData class]]) { - // There is rare race condition where the data stored in the dictionary - // may have been deallocated, which means the module instance is no longer - // usable. - return nil; - } - return moduleData.instance; - } - - // Module may not be loaded yet, so attempt to force load it here. - // Do this only if the bridge is still valid. - if (_didInvalidate) { - return nil; - } - - const BOOL result = [self.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] && - [self.delegate bridge:self didNotFindModule:moduleName]; - if (result) { - // Try again. - moduleData = _moduleDataByName[moduleName]; -#if RCT_DEV - // If the `_moduleDataByName` is nil, it must have been cleared by the reload. - } else if (_moduleDataByName != nil) { - RCTLogError(@"Unable to find module for %@", moduleName); - } -#else - } else { - RCTLogError(@"Unable to find module for %@", moduleName); - } -#endif - - return moduleData.instance; -} - -- (BOOL)moduleIsInitialized:(Class)moduleClass -{ - NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); - if (_moduleDataByName[moduleName].hasInstance) { - return YES; - } - - if (_turboModuleRegistry) { - return [_turboModuleRegistry moduleIsInitialized:[moduleName UTF8String]]; - } - - return NO; -} - -- (id)moduleForClass:(Class)moduleClass -{ - return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass) lazilyLoadIfNecessary:YES]; -} - -- (std::shared_ptr)_buildModuleRegistryUnlocked -{ - if (!self.valid) { - return {}; - } - - [_performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig]; - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge buildModuleRegistry]", nil); - - __weak __typeof(self) weakSelf = self; - ModuleRegistry::ModuleNotFoundCallback moduleNotFoundCallback = ^bool(const std::string &name) { - __strong __typeof(weakSelf) strongSelf = weakSelf; - return [strongSelf.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] && - [strongSelf.delegate bridge:strongSelf didNotFindModule:@(name.c_str())]; - }; - - auto registry = std::make_shared( - createNativeModules(_moduleDataByID, self, _reactInstance), moduleNotFoundCallback); - - [_performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig]; - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - return registry; -} - -- (void)_initializeBridge:(std::shared_ptr)executorFactory - parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget -{ - if (!self.valid) { - return; - } - - __weak RCTCxxBridge *weakSelf = self; - _jsMessageThread = std::make_shared([NSRunLoop currentRunLoop], ^(NSError *error) { - if (error) { - [weakSelf handleError:error]; - } - }); - - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initializeBridge:]", nil); - // This can only be false if the bridge was invalidated before startup completed - if (_reactInstance) { -#if RCT_DEV - executorFactory = std::make_shared(self, executorFactory); -#endif - - [self _initializeBridgeLocked:executorFactory parentInspectorTarget:parentInspectorTarget]; - -#if RCT_PROFILE - if (RCTProfileIsProfiling()) { - _reactInstance->setGlobalVariable("__RCTProfileIsProfiling", std::make_unique("true")); - } -#endif - } - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -- (void)_initializeBridgeLocked:(std::shared_ptr)executorFactory - parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget -{ - std::lock_guard guard(_moduleRegistryLock); - - // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance - _reactInstance->initializeBridge( - std::make_unique(self), - executorFactory, - _jsMessageThread, - [self _buildModuleRegistryUnlocked], - parentInspectorTarget); - _moduleRegistryCreated = YES; -} - -- (void)updateModuleWithInstance:(id)instance -{ - NSString *const moduleName = RCTBridgeModuleNameForClass([instance class]); - if (moduleName) { - RCTModuleData *const moduleData = _moduleDataByName[moduleName]; - if (moduleData) { - moduleData.instance = instance; - } - } -} - -- (NSArray *)registerModulesForClasses:(NSArray *)moduleClasses -{ - return [self _registerModulesForClasses:moduleClasses lazilyDiscovered:NO]; -} - -- (NSArray *)_registerModulesForClasses:(NSArray *)moduleClasses - lazilyDiscovered:(BOOL)lazilyDiscovered -{ - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil); - - NSArray *moduleClassesCopy = [moduleClasses copy]; - NSMutableArray *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count]; - for (Class moduleClass in moduleClassesCopy) { - if (RCTTurboModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) { - continue; - } - NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); - - // Check for module name collisions - RCTModuleData *moduleData = _moduleDataByName[moduleName]; - if (moduleData) { - if (moduleData.hasInstance || lazilyDiscovered) { - // Existing module was preregistered, so it takes precedence - continue; - } else if ([moduleClass new] == nil) { - // The new module returned nil from init, so use the old module - continue; - } else if ([moduleData.moduleClass new] != nil) { - // Both modules were non-nil, so it's unclear which should take precedence - RCTLogWarn( - @"Attempted to register RCTBridgeModule class %@ for the " - "name '%@', but name was already registered by class %@", - moduleClass, - moduleName, - moduleData.moduleClass); - } - } - - // Instantiate moduleData - // TODO #13258411: can we defer this until config generation? - int32_t moduleDataId = getUniqueId(); - BridgeNativeModulePerfLogger::moduleDataCreateStart([moduleName UTF8String], moduleDataId); - moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass - bridge:self - moduleRegistry:_objCModuleRegistry - viewRegistry_DEPRECATED:_viewRegistry_DEPRECATED - bundleManager:_bundleManager - callableJSModules:_callableJSModules]; - moduleData.callInvokerProvider = self; - BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId); - - _moduleDataByName[moduleName] = moduleData; - [_moduleClassesByID addObject:moduleClass]; - [moduleDataByID addObject:moduleData]; - } - [_moduleDataByID addObjectsFromArray:moduleDataByID]; - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - return moduleDataByID; -} - -- (void)registerExtraModules -{ - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] extraModules", nil); - - NSArray> *appExtraModules = nil; - if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) { - appExtraModules = [self.delegate extraModulesForBridge:_parentBridge]; - } else if (self.moduleProvider) { - appExtraModules = self.moduleProvider(); - } - - NSMutableArray> *extraModules = [NSMutableArray new]; - - // Prevent TurboModules from appearing the NativeModule system - for (id module in appExtraModules) { - if (!(RCTTurboModuleEnabled() && [module conformsToProtocol:@protocol(RCTTurboModule)])) { - [extraModules addObject:module]; - } - } - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] preinitialized moduleData", nil); - // Set up moduleData for pre-initialized module instances - for (id module in extraModules) { - Class moduleClass = [module class]; - NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); - - if (RCT_DEBUG) { - // Check for name collisions between preregistered modules - RCTModuleData *moduleData = _moduleDataByName[moduleName]; - if (moduleData) { - RCTLogError( - @"Attempted to register RCTBridgeModule class %@ for the " - "name '%@', but name was already registered by class %@", - moduleClass, - moduleName, - moduleData.moduleClass); - continue; - } - } - - if (RCTTurboModuleEnabled() && [module conformsToProtocol:@protocol(RCTTurboModule)]) { -#if RCT_DEBUG - // TODO: don't ask for extra module for when TurboModule is enabled. - RCTLogError( - @"NativeModule '%@' was marked as TurboModule, but provided as an extra NativeModule " - "by the class '%@', ignoring.", - moduleName, - moduleClass); -#endif - continue; - } - - // Instantiate moduleData container - int32_t moduleDataId = getUniqueId(); - BridgeNativeModulePerfLogger::moduleDataCreateStart([moduleName UTF8String], moduleDataId); - RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module - bridge:self - moduleRegistry:_objCModuleRegistry - viewRegistry_DEPRECATED:_viewRegistry_DEPRECATED - bundleManager:_bundleManager - callableJSModules:_callableJSModules]; - BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId); - - _moduleDataByName[moduleName] = moduleData; - [_moduleClassesByID addObject:moduleClass]; - [_moduleDataByID addObject:moduleData]; - } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -- (NSArray *)_initializeModules:(NSArray *)modules - withDispatchGroup:(dispatch_group_t)dispatchGroup - lazilyDiscovered:(BOOL)lazilyDiscovered -{ - // Set up moduleData for automatically-exported modules - NSArray *moduleDataById = [self _registerModulesForClasses:modules - lazilyDiscovered:lazilyDiscovered]; - - if (lazilyDiscovered) { -#if RCT_DEBUG - // Lazily discovered modules do not require instantiation here, - // as they are not allowed to have pre-instantiated instance - // and must not require the main queue. - for (RCTModuleData *moduleData in moduleDataById) { - RCTAssert( - !(moduleData.requiresMainQueueSetup || moduleData.hasInstance), - @"Module \'%@\' requires initialization on the Main Queue or has pre-instantiated, which is not supported for the lazily discovered modules.", - moduleData.name); - } -#endif - } else { - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] moduleData.hasInstance", nil); - // Dispatch module init onto main thread for those modules that require it - // For non-lazily discovered modules we run through the entire set of modules - // that we have, otherwise some modules coming from the delegate - // or module provider block, will not be properly instantiated. - for (RCTModuleData *moduleData in _moduleDataByID) { - if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) { - // Modules that were pre-initialized should ideally be set up before - // bridge init has finished, otherwise the caller may try to access the - // module directly rather than via `[bridge moduleForClass:]`, which won't - // trigger the lazy initialization process. If the module cannot safely be - // set up on the current thread, it will instead be async dispatched - // to the main thread to be set up in _prepareModulesWithDispatchGroup:. - (void)[moduleData instance]; - } - } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - // From this point on, RCTDidInitializeModuleNotification notifications will - // be sent the first time a module is accessed. - _moduleSetupComplete = YES; - [self _prepareModulesWithDispatchGroup:dispatchGroup]; - } - -#if RCT_PROFILE - if (RCTProfileIsProfiling()) { - // Depends on moduleDataByID being loaded - RCTProfileHookModules(self); - } -#endif - return moduleDataById; -} - -- (void)registerAdditionalModuleClasses:(NSArray *)modules -{ - std::lock_guard guard(_moduleRegistryLock); - if (_moduleRegistryCreated) { - NSArray *newModules = [self _initializeModules:modules - withDispatchGroup:NULL - lazilyDiscovered:YES]; - assert(_reactInstance); // at this point you must have reactInstance as you already called - // reactInstance->initializeBridge - _reactInstance->getModuleRegistry().registerModules(createNativeModules(newModules, self, _reactInstance)); - } else { - [self registerModulesForClasses:modules]; - } -} - -- (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup -{ - RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTCxxBridge _prepareModulesWithDispatchGroup]", nil); - - BOOL initializeImmediately = NO; - if (dispatchGroup == NULL) { - // If no dispatchGroup is passed in, we must prepare everything immediately. - // We better be on the right thread too. - RCTAssertMainQueue(); - initializeImmediately = YES; - } - - // Set up modules that require main thread init or constants export - [_performanceLogger setValue:0 forTag:RCTPLNativeModuleMainThread]; - - for (RCTModuleData *moduleData in _moduleDataByID) { - if (moduleData.requiresMainQueueSetup) { - // Modules that need to be set up on the main thread cannot be initialized - // lazily when required without doing a dispatch_sync to the main thread, - // which can result in deadlock. To avoid this, we initialize all of these - // modules on the main thread in parallel with loading the JS code, so - // they will already be available before they are ever required. - dispatch_block_t block = ^{ - if (self.valid) { - [self->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread]; - (void)[moduleData instance]; - [moduleData gatherConstants]; - [self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread]; - } - }; - - if (initializeImmediately && RCTIsMainQueue()) { - block(); - } else { - // We've already checked that dispatchGroup is non-null, but this satisfies the - // Xcode analyzer - if (dispatchGroup) { - dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block); - } - } - _modulesInitializedOnMainQueue++; - } - } - [_performanceLogger setValue:_modulesInitializedOnMainQueue forTag:RCTPLNativeModuleMainThreadUsesCount]; - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -- (void)registerModuleForFrameUpdates:(id)module withModuleData:(RCTModuleData *)moduleData -{ - id moduleHolder = - [[RCTBridgeDisplayLinkModuleHolder alloc] initWithModuleData:moduleData]; - [_displayLink registerModuleForFrameUpdates:module withModuleHolder:moduleHolder]; -} - -- (void)executeSourceCode:(NSData *)sourceCode withSourceURL:(NSURL *)url sync:(BOOL)sync -{ - // This will get called from whatever thread was actually executing JS. - dispatch_block_t completion = ^{ - // Log start up metrics early before processing any other js calls - [self logStartupFinish]; - // Flush pending calls immediately so we preserve ordering - [self _flushPendingCalls]; - - // Perform the state update and notification on the main thread, so we can't run into - // timing issues with RCTRootView - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification - object:self->_parentBridge - userInfo:@{@"bridge" : self}]; - - // Starting the display link is not critical to startup, so do it last - [self ensureOnJavaScriptThread:^{ - // Register the display link to start sending js calls after everything is setup - [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]]; - }]; - }); - }; - - if (sync) { - [self executeApplicationScriptSync:sourceCode url:url]; - completion(); - } else { - [self enqueueApplicationScript:sourceCode url:url onComplete:completion]; - } - - // Use the original request URL here - HMRClient uses this to derive the /hot URL and entry point. - [self.devSettings setupHMRClientWithBundleURL:self.bundleURL]; -} - -- (void)handleError:(NSError *)error -{ - // This is generally called when the infrastructure throws an - // exception while calling JS. Most product exceptions will not go - // through this method, but through RCTExceptionManager. - - // There are three possible states: - // 1. initializing == _valid && _loading - // 2. initializing/loading finished (success or failure) == _valid && !_loading - // 3. invalidated == !_valid && !_loading - - // !_valid && _loading can't happen. - - // In state 1: on main queue, move to state 2, reset the bridge, and RCTFatal. - // In state 2: go directly to RCTFatal. Do not enqueue, do not collect $200. - // In state 3: do nothing. - - if (self->_valid && !self->_loading) { - if ([error userInfo][RCTJSRawStackTraceKey]) { - RCTRedBox *redBox = RCTRedBoxGetEnabled() ? [self.moduleRegistry moduleForName:"RedBox"] : nil; - [redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]]; - } - - RCTFatal(error); - - // RN will stop, but let the rest of the app keep going. - return; - } - - if (!_valid || !_loading) { - return; - } - - // Hack: once the bridge is invalidated below, it won't initialize any new native - // modules. Initialize the redbox module now so we can still report this error. - RCTRedBox *redBox = RCTRedBoxGetEnabled() ? [self.moduleRegistry moduleForName:"RedBox"] : nil; - - _loading = NO; - _valid = NO; - _moduleRegistryCreated = NO; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (self->_jsMessageThread) { - // Make sure initializeBridge completed - self->_jsMessageThread->runOnQueueSync([] {}); - } - - self->_reactInstance.reset(); - self->_jsMessageThread.reset(); - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification - object:self->_parentBridge - userInfo:@{@"bridge" : self, @"error" : error}]; - - if ([error userInfo][RCTJSRawStackTraceKey]) { - [redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]]; - } - - RCTFatal(error); - }); -} - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithDelegate : (__unused id)delegate bundleURL : ( - __unused NSURL *)bundleURL moduleProvider : (__unused RCTBridgeModuleListProvider) - block launchOptions : (__unused NSDictionary *)launchOptions) - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL : (__unused NSURL *)bundleURL moduleProvider : ( - __unused RCTBridgeModuleListProvider)block launchOptions : (__unused NSDictionary *)launchOptions) - -/** - * Prevent super from calling setUp (that'd create another batchedBridge) - */ -- (void)setUp -{ -} - -- (Class)executorClass -{ - return _parentBridge.executorClass; -} - -- (void)setExecutorClass:(Class)executorClass -{ - RCTAssertMainQueue(); - - _parentBridge.executorClass = executorClass; -} - -- (NSURL *)bundleURL -{ - return _parentBridge.bundleURL; -} - -- (void)setBundleURL:(NSURL *)bundleURL -{ - _parentBridge.bundleURL = bundleURL; -} - -- (id)delegate -{ - return _parentBridge.delegate; -} - -- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue -{ - if (queue == RCTJSThread) { - [self ensureOnJavaScriptThread:block]; - } else if (queue) { - dispatch_async(queue, block); - } -} - -#pragma mark - RCTInvalidating - -- (void)invalidate -{ - if (_didInvalidate) { - return; - } - - RCTAssertMainQueue(); - RCTLogInfo(@"Invalidating %@ (parent: %@, executor: %@)", self, _parentBridge, [self executorClass]); - - if (self->_reactInstance) { - // Do this synchronously on the main thread to fulfil unregisterFromInspector's - // requirements. - self->_reactInstance->unregisterFromInspector(); - } - - _loading = NO; - _valid = NO; - _didInvalidate = YES; - _moduleRegistryCreated = NO; - - if ([RCTBridge currentBridge] == self) { - [RCTBridge setCurrentBridge:nil]; - } - - // Stop JS instance and message thread - [self ensureOnJavaScriptThread:^{ - [self->_displayLink invalidate]; - self->_displayLink = nil; - - if (RCTProfileIsProfiling()) { - RCTProfileUnhookModules(self); - } - - // Invalidate modules - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeWillInvalidateModulesNotification - object:self->_parentBridge - userInfo:@{@"bridge" : self}]; - - // We're on the JS thread (which we'll be suspending soon), so no new calls will be made to native modules after - // this completes. We must ensure all previous calls were dispatched before deallocating the instance (and module - // wrappers) or we may have invalid pointers still in flight. - dispatch_group_t moduleInvalidation = dispatch_group_create(); - for (RCTModuleData *moduleData in self->_moduleDataByID) { - // Be careful when grabbing an instance here, we don't want to instantiate - // any modules just to invalidate them. - if (![moduleData hasInstance]) { - continue; - } - - if ([moduleData.instance respondsToSelector:@selector(invalidate)]) { - dispatch_group_enter(moduleInvalidation); - [self - dispatchBlock:^{ - [(id)moduleData.instance invalidate]; - dispatch_group_leave(moduleInvalidation); - } - queue:moduleData.methodQueue]; - } - [moduleData invalidate]; - } - - if (dispatch_group_wait(moduleInvalidation, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC))) { - RCTLogError(@"Timed out waiting for modules to be invalidated"); - } - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeDidInvalidateModulesNotification - object:self->_parentBridge - userInfo:@{@"bridge" : self}]; - - self->_reactInstance.reset(); - self->_jsMessageThread.reset(); - - self->_moduleDataByName = nil; - self->_moduleDataByID = nil; - self->_moduleClassesByID = nil; - self->_pendingCalls = nil; - - [self->_jsThread cancel]; - self->_jsThread = nil; - CFRunLoopStop(CFRunLoopGetCurrent()); - }]; -} - -- (void)logMessage:(NSString *)message level:(NSString *)level -{ - if (RCT_DEBUG && _valid) { - [self enqueueJSCall:@"RCTLog" method:@"logIfNoNativeHook" args:@[ level, message ] completion:NULL]; - } -} - -#pragma mark - RCTBridge methods - -- (void)_runAfterLoad:(RCTPendingCall)block -{ - // Ordering here is tricky. Ideally, the C++ bridge would provide - // functionality to defer calls until after the app is loaded. Until that - // happens, we do this. _pendingCount keeps a count of blocks which have - // been deferred. It is incremented using an atomic barrier call before each - // block is added to the js queue, and decremented using an atomic barrier - // call after the block is executed. If _pendingCount is zero, there is no - // work either in the js queue, or in _pendingCalls, so it is safe to add new - // work to the JS queue directly. - - if (self.loading || _pendingCount > 0) { - // From the callers' perspective: - - // Phase 1: jsQueueBlocks are added to the queue; _pendingCount is - // incremented for each. If the first block is created after self.loading is - // true, phase 1 will be nothing. - _pendingCount++; - dispatch_block_t jsQueueBlock = ^{ - // From the perspective of the JS queue: - if (self.loading) { - // Phase A: jsQueueBlocks are executed. self.loading is true, so they - // are added to _pendingCalls. - [self->_pendingCalls addObject:block]; - } else { - // Phase C: More jsQueueBlocks are executed. self.loading is false, so - // each block is executed, adding work to the queue, and _pendingCount is - // decremented. - block(); - self->_pendingCount--; - } - }; - [self ensureOnJavaScriptThread:jsQueueBlock]; - } else { - // Phase 2/Phase D: blocks are executed directly, adding work to the JS queue. - block(); - } -} - -- (void)logStartupFinish -{ - // Log metrics about native requires during the bridge startup. - uint64_t nativeRequiresCount = [_performanceLogger valueForTag:RCTPLRAMNativeRequiresCount]; - [_performanceLogger setValue:nativeRequiresCount forTag:RCTPLRAMStartupNativeRequiresCount]; - uint64_t nativeRequires = [_performanceLogger valueForTag:RCTPLRAMNativeRequires]; - [_performanceLogger setValue:nativeRequires forTag:RCTPLRAMStartupNativeRequires]; - - [_performanceLogger markStopForTag:RCTPLBridgeStartup]; -} - -- (void)_flushPendingCalls -{ - RCT_PROFILE_BEGIN_EVENT(0, @"Processing pendingCalls", @{@"count" : [@(_pendingCalls.count) stringValue]}); - // Phase B: _flushPendingCalls happens. Each block in _pendingCalls is - // executed, adding work to the queue, and _pendingCount is decremented. - // loading is set to NO. - NSArray *pendingCalls = _pendingCalls; - _pendingCalls = nil; - for (RCTPendingCall call in pendingCalls) { - call(); - _pendingCount--; - } - _loading = NO; - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -/** - * Public. Can be invoked from any thread. - */ -- (void)enqueueJSCall:(NSString *)module - method:(NSString *)method - args:(NSArray *)args - completion:(dispatch_block_t)completion -{ - if (!self.valid) { - return; - } - - /** - * AnyThread - */ - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueJSCall:]", nil); - - RCTProfileBeginFlowEvent(); - __weak __typeof(self) weakSelf = self; - [self _runAfterLoad:^() { - RCTProfileEndFlowEvent(); - __strong __typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (strongSelf->_reactInstance) { - strongSelf->_reactInstance->callJSFunction( - [module UTF8String], [method UTF8String], convertIdToFollyDynamic(args ?: @[])); - - // ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure - // the block is invoked after callJSFunction - if (completion) { - if (strongSelf->_jsMessageThread) { - strongSelf->_jsMessageThread->runOnQueue(completion); - } else { - RCTLogWarn(@"Can't invoke completion without messageThread"); - } - } - } - }]; - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -/** - * Called by RCTModuleMethod from any thread. - */ -- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args -{ - if (!self.valid) { - return; - } - - /** - * AnyThread - */ - - RCTProfileBeginFlowEvent(); - __weak __typeof(self) weakSelf = self; - [self _runAfterLoad:^() { - RCTProfileEndFlowEvent(); - __strong __typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (strongSelf->_reactInstance) { - strongSelf->_reactInstance->callJSCallback([cbID unsignedLongLongValue], convertIdToFollyDynamic(args ?: @[])); - } - }]; -} - -/** - * Private hack to support `setTimeout(fn, 0)` - */ -- (void)_immediatelyCallTimer:(NSNumber *)timer -{ - if (_reactInstance) { - _reactInstance->callJSFunction( - "JSTimers", "callTimers", folly::dynamic::array(folly::dynamic::array([timer doubleValue]))); - } -} - -- (void)enqueueApplicationScript:(NSData *)script url:(NSURL *)url onComplete:(dispatch_block_t)onComplete -{ - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueApplicationScript]", nil); - - [self executeApplicationScript:script url:url async:YES]; - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - - // Assumes that onComplete can be called when the next block on the JS thread is scheduled - if (onComplete) { - RCTAssert(_jsMessageThread != nullptr, @"Cannot invoke completion without jsMessageThread"); - _jsMessageThread->runOnQueue(onComplete); - } -} - -- (void)executeApplicationScriptSync:(NSData *)script url:(NSURL *)url -{ - [self executeApplicationScript:script url:url async:NO]; -} - -- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async -{ - [self _tryAndHandleError:^{ - NSString *sourceUrlStr = deriveSourceURL(url); - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartExecutingNotification - object:self->_parentBridge - userInfo:@{@"bridge" : self}]; - - BundleHeader header; - [script getBytes:&header length:sizeof(header)]; - ScriptTag scriptType = parseTypeFromHeader(header); - - // hold a local reference to reactInstance in case a parallel thread - // resets it between null check and usage - auto reactInstance = self->_reactInstance; - if (scriptType == ScriptTag::RAMBundle) { - [self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad]; - auto ramBundle = std::make_unique(sourceUrlStr.UTF8String); - std::unique_ptr scriptStr = ramBundle->getStartupCode(); - [self->_performanceLogger markStopForTag:RCTPLRAMBundleLoad]; - [self->_performanceLogger setValue:scriptStr->size() forTag:RCTPLRAMStartupCodeSize]; - if (reactInstance) { - auto registry = - RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory()); - reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr), sourceUrlStr.UTF8String, !async); - } - } else if (reactInstance) { - reactInstance->loadScriptFromString(std::make_unique(script), sourceUrlStr.UTF8String, !async); - } else { - std::string methodName = async ? "loadBundle" : "loadBundleSync"; - throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge"); - } - }]; -} - -- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path -{ - if (_reactInstance) { - _reactInstance->registerBundle(static_cast(segmentId), path.UTF8String); - } -} - -#pragma mark - Payload Processing - -- (void)batchDidComplete -{ - // TODO #12592471: batchDidComplete is only used by RCTUIManager, - // can we eliminate this special case? - for (RCTModuleData *moduleData in _moduleDataByID) { - if (moduleData.implementsBatchDidComplete) { - [self - dispatchBlock:^{ - [moduleData.instance batchDidComplete]; - } - queue:moduleData.methodQueue]; - } - } -} - -- (void)startProfiling -{ - RCTAssertMainQueue(); - - [self ensureOnJavaScriptThread:^{ - RCTProfileInit(self); - - [self enqueueJSCall:@"Systrace" method:@"setEnabled" args:@[ @YES ] completion:NULL]; - }]; -} - -- (void)stopProfiling:(void (^)(NSData *))callback -{ - RCTAssertMainQueue(); - - [self ensureOnJavaScriptThread:^{ - [self enqueueJSCall:@"Systrace" method:@"setEnabled" args:@[ @NO ] completion:NULL]; - RCTProfileEnd(self, ^(NSString *log) { - NSData *logData = [log dataUsingEncoding:NSUTF8StringEncoding]; - callback(logData); - }); - }]; -} - -- (BOOL)isBatchActive -{ - return _reactInstance ? _reactInstance->isBatchActive() : NO; -} - -- (void *)runtime -{ - if (!_reactInstance) { - return nullptr; - } - - return _reactInstance->getJavaScriptContext(); -} - -- (void)invokeAsync:(CallFunc &&)func -{ - __block auto retainedFunc = std::move(func); - __weak __typeof(self) weakSelf = self; - [self _runAfterLoad:^{ - __strong __typeof(self) strongSelf = weakSelf; - - if (std::shared_ptr jsInvoker = strongSelf.jsCallInvoker) { - jsInvoker->invokeAsync(std::move(retainedFunc)); - } - }]; -} - -#pragma mark - RCTBridge (RCTTurboModule) - -- (std::shared_ptr)jsCallInvoker -{ - return _reactInstance ? _reactInstance->getJSCallInvoker() : nullptr; -} - -- (std::shared_ptr)decorateNativeMethodCallInvoker: - (std::shared_ptr)nativeInvoker -{ - return _reactInstance ? _reactInstance->getDecoratedNativeMethodCallInvoker(nativeInvoker) : nullptr; -} - -#pragma mark - RCTModuleDataCallInvokerProvider - -- (RCTCallInvoker *)callInvokerForModuleData:(RCTModuleData *)moduleData -{ - return [[RCTCallInvoker alloc] initWithCallInvoker:self.jsCallInvoker]; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH @implementation RCTCxxBridge @end -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.h b/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.h index 8515e6e8560..f852921e0ab 100644 --- a/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.h +++ b/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.h @@ -6,20 +6,3 @@ */ #pragma once - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#include - -namespace facebook::react { - -/** - * Creates a lambda used to bind a JSIRuntime in the context of - * Apple platforms, such as console logging, performance metrics, etc. - */ -[[deprecated("This API will be removed along with the legacy architecture.")]] -JSIExecutor::RuntimeInstaller RCTJSIExecutorRuntimeInstaller(JSIExecutor::RuntimeInstaller runtimeInstallerToWrap); - -} // namespace facebook::react - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.mm b/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.mm index 000d3544881..37bd01c7f74 100644 --- a/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.mm +++ b/packages/react-native/React/CxxBridge/RCTJSIExecutorRuntimeInstaller.mm @@ -6,29 +6,3 @@ */ #include "RCTJSIExecutorRuntimeInstaller.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#include - -namespace facebook::react { - -JSIExecutor::RuntimeInstaller RCTJSIExecutorRuntimeInstaller(JSIExecutor::RuntimeInstaller runtimeInstallerToWrap) -{ - return [runtimeInstaller = runtimeInstallerToWrap](jsi::Runtime &runtime) { - Logger iosLoggingBinder = [](const std::string &message, unsigned int logLevel) { - _RCTLogJavaScriptInternal(static_cast(logLevel), [NSString stringWithUTF8String:message.c_str()]); - }; - bindNativeLogger(runtime, iosLoggingBinder); - - // Wrap over the original runtimeInstaller - if (runtimeInstaller) { - runtimeInstaller(runtime); - } - }; -} - -} // namespace facebook::react - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CxxBridge/RCTObjcExecutor.h b/packages/react-native/React/CxxBridge/RCTObjcExecutor.h index 9074644fdfd..22a77615600 100644 --- a/packages/react-native/React/CxxBridge/RCTObjcExecutor.h +++ b/packages/react-native/React/CxxBridge/RCTObjcExecutor.h @@ -4,31 +4,3 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#include -#include - -#import -#import -#import - -namespace facebook::react { - -class [[deprecated("This API will be removed along with the legacy architecture.")]] RCTObjcExecutorFactory - : public JSExecutorFactory { - public: - RCTObjcExecutorFactory(id jse, RCTJavaScriptCompleteBlock errorBlock); - std::unique_ptr createJSExecutor( - std::shared_ptr delegate, - std::shared_ptr jsQueue) override; - - private: - id m_jse; - RCTJavaScriptCompleteBlock m_errorBlock; -}; - -} // namespace facebook::react - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm b/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm index fa8e0411797..e53d9477e35 100644 --- a/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm +++ b/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm @@ -6,145 +6,3 @@ */ #import "RCTObjcExecutor.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -namespace facebook::react { - -namespace { - -class JSEException : public std::runtime_error { - public: - JSEException(NSError *error) : runtime_error([[error description] UTF8String]) {} -}; - -class RCTObjcExecutor : public JSExecutor { - public: - RCTObjcExecutor( - id jse, - RCTJavaScriptCompleteBlock errorBlock, - std::shared_ptr jsThread, - std::shared_ptr delegate) - : m_jse(jse), m_errorBlock(errorBlock), m_delegate(std::move(delegate)), m_jsThread(std::move(jsThread)) - { - m_jsCallback = ^(id json, NSError *error) { - if (error != nullptr) { - // Do not use "m_errorBlock" here as the bridge might be in the middle - // of invalidation as a result of error handling and "this" can be - // already deallocated. - errorBlock(error); - return; - } - - m_jsThread->runOnQueue( - [this, json] { m_delegate->callNativeModules(*this, convertIdToFollyDynamic(json), true); }); - }; - - // Synchronously initialize the executor - [jse setUp]; - - folly::dynamic nativeModuleConfig = folly::dynamic::array; - auto moduleRegistry = m_delegate->getModuleRegistry(); - for (const auto &name : moduleRegistry->moduleNames()) { - auto config = moduleRegistry->getConfig(name); - nativeModuleConfig.push_back(config ? config->config : nullptr); - } - - folly::dynamic config = folly::dynamic::object("remoteModuleConfig", std::move(nativeModuleConfig)); - - setGlobalVariable("__fbBatchedBridgeConfig", std::make_unique(folly::toJson(config))); - } - - void initializeRuntime() override - { - // We do nothing here since initialization is done in the constructor - } - - void loadBundle(std::unique_ptr script, std::string sourceURL) override - { - RCTProfileBeginFlowEvent(); - [m_jse executeApplicationScript:[NSData dataWithBytes:script->c_str() length:script->size()] - sourceURL:[[NSURL alloc] initWithString:@(sourceURL.c_str())] - onComplete:^(NSError *error) { - RCTProfileEndFlowEvent(); - - if (error != nullptr) { - m_errorBlock(error); - return; - } - - [m_jse flushedQueue:m_jsCallback]; - }]; - } - - void setBundleRegistry(std::unique_ptr /*bundleRegistry*/) override - { - RCTAssert(NO, @"RAM bundles are not supported in RCTObjcExecutor"); - } - - void registerBundle(uint32_t __unused bundleId, const std::string __unused &bundlePath) override - { - RCTAssert(NO, @"RAM bundles are not supported in RCTObjcExecutor"); - } - - void callFunction(const std::string &module, const std::string &method, const folly::dynamic &arguments) override - { - [m_jse callFunctionOnModule:@(module.c_str()) - method:@(method.c_str()) - arguments:convertFollyDynamicToId(arguments) - callback:m_jsCallback]; - } - - void invokeCallback(double callbackId, const folly::dynamic &arguments) override - { - [m_jse invokeCallbackID:@(callbackId) arguments:convertFollyDynamicToId(arguments) callback:m_jsCallback]; - } - - virtual void setGlobalVariable(std::string propName, std::unique_ptr jsonValue) override - { - [m_jse injectJSONText:@(jsonValue->c_str()) asGlobalObjectNamed:@(propName.c_str()) callback:m_errorBlock]; - } - - virtual std::string getDescription() override - { - return [NSStringFromClass([m_jse class]) UTF8String]; - } - - private: - id m_jse; - RCTJavaScriptCompleteBlock m_errorBlock; - std::shared_ptr m_delegate; - std::shared_ptr m_jsThread; - RCTJavaScriptCallback m_jsCallback; -}; - -} // namespace - -RCTObjcExecutorFactory::RCTObjcExecutorFactory(id jse, RCTJavaScriptCompleteBlock errorBlock) - : m_jse(jse), m_errorBlock(errorBlock) -{ -} - -std::unique_ptr RCTObjcExecutorFactory::createJSExecutor( - std::shared_ptr delegate, - std::shared_ptr jsQueue) -{ - return std::unique_ptr(new RCTObjcExecutor(m_jse, m_errorBlock, jsQueue, delegate)); -} - -} // namespace facebook::react - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CxxModule/DispatchMessageQueueThread.h b/packages/react-native/React/CxxModule/DispatchMessageQueueThread.h index f082c059830..63622dc942a 100644 --- a/packages/react-native/React/CxxModule/DispatchMessageQueueThread.h +++ b/packages/react-native/React/CxxModule/DispatchMessageQueueThread.h @@ -9,42 +9,3 @@ #include #include - -#ifndef RCT_REMOVE_LEGACY_ARCH - -namespace facebook::react { - -// RCTNativeModule arranges for native methods to be invoked on a queue which -// is not the JS thread. C++ modules don't use RCTNativeModule, so this little -// adapter does the work. - -class [[deprecated("This API will be removed along with the legacy architecture.")]] DispatchMessageQueueThread - : public MessageQueueThread { - public: - DispatchMessageQueueThread(RCTModuleData *moduleData) : moduleData_(moduleData) {} - - void runOnQueue(std::function &&func) override - { - dispatch_queue_t queue = moduleData_.methodQueue; - dispatch_block_t block = [func = std::move(func)] { func(); }; - RCTAssert(block != nullptr, @"Invalid block generated in call to %@", moduleData_); - if (queue && block) { - dispatch_async(queue, block); - } - } - void runOnQueueSync(std::function &&__unused func) override - { - LOG(FATAL) << "Unsupported operation"; - } - void quitSynchronous() override - { - LOG(FATAL) << "Unsupported operation"; - } - - private: - RCTModuleData *moduleData_; -}; - -} // namespace facebook::react - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CxxModule/RCTCxxUtils.h b/packages/react-native/React/CxxModule/RCTCxxUtils.h index e0327342e74..1235c181de0 100644 --- a/packages/react-native/React/CxxModule/RCTCxxUtils.h +++ b/packages/react-native/React/CxxModule/RCTCxxUtils.h @@ -18,14 +18,6 @@ namespace facebook::react { class Instance; class NativeModule; -#ifndef RCT_REMOVE_LEGACY_ARCH - -[[deprecated("This API will be removed along with the legacy architecture.")]] -std::vector> -createNativeModules(NSArray *modules, RCTBridge *bridge, const std::shared_ptr &instance); - -#endif // RCT_REMOVE_LEGACY_ARCH - NSError *tryAndReturnError(const std::function &func); NSString *deriveSourceURL(NSURL *url); diff --git a/packages/react-native/React/CxxModule/RCTCxxUtils.mm b/packages/react-native/React/CxxModule/RCTCxxUtils.mm index 820b87dd1f8..cf65ba7860e 100644 --- a/packages/react-native/React/CxxModule/RCTCxxUtils.mm +++ b/packages/react-native/React/CxxModule/RCTCxxUtils.mm @@ -19,20 +19,6 @@ using facebook::jsi::JSError; -#ifndef RCT_REMOVE_LEGACY_ARCH - -std::vector> -createNativeModules(NSArray *modules, RCTBridge *bridge, const std::shared_ptr &instance) -{ - std::vector> nativeModules; - for (RCTModuleData *moduleData in modules) { - nativeModules.emplace_back(std::make_unique(bridge, moduleData)); - } - return nativeModules; -} - -#endif // RCT_REMOVE_LEGACY_ARCH - static NSError *errorWithException(const std::exception &e) { NSString *msg = @(e.what()); diff --git a/packages/react-native/React/CxxModule/RCTNativeModule.h b/packages/react-native/React/CxxModule/RCTNativeModule.h index 3b08413e356..cf073ad79fa 100644 --- a/packages/react-native/React/CxxModule/RCTNativeModule.h +++ b/packages/react-native/React/CxxModule/RCTNativeModule.h @@ -7,28 +7,3 @@ #import #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -namespace facebook::react { - -class [[deprecated("This API will be removed along with the legacy architecture.")]] RCTNativeModule - : public NativeModule { - public: - RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData); - - std::string getName() override; - std::string getSyncMethodName(unsigned int methodId) override; - std::vector getMethods() override; - folly::dynamic getConstants() override; - void invoke(unsigned int methodId, folly::dynamic &¶ms, int callId) override; - MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &¶ms) override; - - private: - __weak RCTBridge *m_bridge; - RCTModuleData *m_moduleData; -}; - -} // namespace facebook::react - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/CxxModule/RCTNativeModule.mm b/packages/react-native/React/CxxModule/RCTNativeModule.mm index 9d35eb72b99..c3030c60e0c 100644 --- a/packages/react-native/React/CxxModule/RCTNativeModule.mm +++ b/packages/react-native/React/CxxModule/RCTNativeModule.mm @@ -6,236 +6,3 @@ */ #import "RCTNativeModule.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#ifdef WITH_FBSYSTRACE -#include -#endif - -namespace { -enum SchedulingContext { Sync, Async }; -} - -namespace facebook::react { - -static MethodCallResult invokeInner( - RCTBridge *bridge, - RCTModuleData *moduleData, - unsigned int methodId, - const folly::dynamic ¶ms, - int callId, - SchedulingContext context); - -RCTNativeModule::RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData) - : m_bridge(bridge), m_moduleData(moduleData) -{ -} - -std::string RCTNativeModule::getName() -{ - return [m_moduleData.name UTF8String]; -} - -std::string RCTNativeModule::getSyncMethodName(unsigned int methodId) -{ - return m_moduleData.methods[methodId].JSMethodName; -} - -std::vector RCTNativeModule::getMethods() -{ - std::vector descs; - - for (id method in m_moduleData.methods) { - descs.emplace_back(method.JSMethodName, RCTFunctionDescriptorFromType(method.functionType)); - } - - return descs; -} - -folly::dynamic RCTNativeModule::getConstants() -{ - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTNativeModule getConstants] moduleData.exportedConstants", nil); - NSDictionary *constants = m_moduleData.exportedConstants; - folly::dynamic ret = convertIdToFollyDynamic(constants); - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - return ret; -} - -void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &¶ms, int callId) -{ - id method = m_moduleData.methods[methodId]; - if (method != nullptr) { - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, - @"[RCTNativeModule invoke]", - @{@"method" : [NSString stringWithUTF8String:method.JSMethodName]}); - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } - - const char *moduleName = [m_moduleData.name UTF8String]; - const char *methodName = m_moduleData.methods[methodId].JSMethodName; - - dispatch_queue_t queue = m_moduleData.methodQueue; - const bool isSyncModule = queue == RCTJSThread; - - if (isSyncModule) { - BridgeNativeModulePerfLogger::syncMethodCallStart(moduleName, methodName); - BridgeNativeModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName); - } else { - BridgeNativeModulePerfLogger::asyncMethodCallStart(moduleName, methodName); - } - - // capture by weak pointer so that we can safely use these variables in a callback - __weak RCTBridge *weakBridge = m_bridge; - __weak RCTModuleData *weakModuleData = m_moduleData; - // The BatchedBridge version of this buckets all the callbacks by thread, and - // queues one block on each. This is much simpler; we'll see how it goes and - // iterate. - dispatch_block_t block = [weakBridge, weakModuleData, methodId, params = std::move(params), callId, isSyncModule] { -#ifdef WITH_FBSYSTRACE - if (callId != -1) { - fbsystrace_end_async_flow(TRACE_TAG_REACT, "native", callId); - } -#else - (void)(callId); -#endif - @autoreleasepool { - invokeInner(weakBridge, weakModuleData, methodId, std::move(params), callId, isSyncModule ? Sync : Async); - } - }; - - if (isSyncModule) { - block(); - BridgeNativeModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName); - } else if (queue != nullptr) { - BridgeNativeModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName); - dispatch_async(queue, block); - } - -#ifdef RCT_DEV - if (queue == nullptr) { - RCTLog( - @"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) without a method queue.", - methodId, - m_moduleData.name); - } -#endif - - if (isSyncModule) { - BridgeNativeModulePerfLogger::syncMethodCallEnd(moduleName, methodName); - } else { - BridgeNativeModulePerfLogger::asyncMethodCallEnd(moduleName, methodName); - } -} - -MethodCallResult RCTNativeModule::callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &¶ms) -{ - return invokeInner(m_bridge, m_moduleData, reactMethodId, params, 0, Sync); -} - -static MethodCallResult invokeInner( - RCTBridge *bridge, - RCTModuleData *moduleData, - unsigned int methodId, - const folly::dynamic ¶ms, - int callId, - SchedulingContext context) -{ - if ((bridge == nullptr) || !bridge.valid || (moduleData == nullptr)) { - if (context == Sync) { - /** - * NOTE: moduleName and methodName are "". This shouldn't be an issue because there can only be one ongoing sync - * call at a time, and when we call syncMethodCallFail, that one call should terminate. This is also an - * exceptional scenario, so it shouldn't occur often. - */ - BridgeNativeModulePerfLogger::syncMethodCallFail("N/A", "N/A"); - } - return std::nullopt; - } - - id method = moduleData.methods[methodId]; - if (RCT_DEBUG && (method == nullptr)) { - RCTLogError(@"Unknown methodID: %ud for module: %@", methodId, moduleData.name); - } - - const char *moduleName = [moduleData.name UTF8String]; - const char *methodName = moduleData.methods[methodId].JSMethodName; - - if (context == Async) { - BridgeNativeModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodName, (int32_t)callId); - BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionStart(moduleName, methodName, (int32_t)callId); - } - - NSArray *objcParams = convertFollyDynamicToId(params); - - if (context == Sync) { - BridgeNativeModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName); - } - - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, - @"[RCTNativeModule invokeInner]", - @{@"method" : [NSString stringWithUTF8String:method.JSMethodName]}); - @try { - if (context == Sync) { - BridgeNativeModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodName); - } else { - BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionEnd(moduleName, methodName, (int32_t)callId); - } - - id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams]; - - if (context == Sync) { - BridgeNativeModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodName); - BridgeNativeModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName); - } else { - BridgeNativeModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodName, (int32_t)callId); - } - - return convertIdToFollyDynamic(result); - } @catch (NSException *exception) { - if (context == Sync) { - BridgeNativeModulePerfLogger::syncMethodCallFail(moduleName, methodName); - } else { - BridgeNativeModulePerfLogger::asyncMethodCallExecutionFail(moduleName, methodName, (int32_t)callId); - } - - // Pass on JS exceptions - if ([exception.name hasPrefix:RCTFatalExceptionName]) { - @throw exception; - } - -#if RCT_DEBUG - NSString *message = [NSString - stringWithFormat:@"Exception '%@' was thrown while invoking %s on target %@ with params %@\ncallstack: %@", - exception, - method.JSMethodName, - moduleData.name, - objcParams, - exception.callStackSymbols]; - RCTFatal(RCTErrorWithMessage(message)); -#else - RCTFatalException(exception); -#endif - } @finally { - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } - - return std::nullopt; -} - -} // namespace facebook::react - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Modules/RCTUIManager.mm b/packages/react-native/React/Modules/RCTUIManager.mm index 3ba6449f53e..69eb981948c 100644 --- a/packages/react-native/React/Modules/RCTUIManager.mm +++ b/packages/react-native/React/Modules/RCTUIManager.mm @@ -133,1501 +133,6 @@ return moduleConstants; } -#ifndef RCT_REMOVE_LEGACY_ARCH - -static void RCTTraverseViewNodes(id view, void (^block)(id)) -{ - if (view.reactTag) { - block(view); - - for (id 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 *_rootViewTags; - NSMutableArray *_pendingUIBlocks; - - // Animation - RCTLayoutAnimationGroup *_layoutAnimationGroup; // Main thread only - - NSMutableDictionary *_shadowViewRegistry; // RCT thread only - NSMutableDictionary *_viewRegistry; // Main thread only - NSMapTable *_nativeIDRegistry; - - NSMapTable *> *_shadowViewsWithUpdatedProps; // UIManager queue only. - NSHashTable *_shadowViewsWithUpdatedChildren; // UIManager queue only. - - // Keyed by viewName - NSMutableDictionary *_componentDataByName; -} - -@synthesize bridge = _bridge; -@synthesize moduleRegistry = _moduleRegistry; - -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); - NSMutableDictionary> *viewRegistry = - (NSMutableDictionary> *)self->_viewRegistry; - for (NSNumber *rootViewTag in self->_rootViewTags) { - id rootView = viewRegistry[rootViewTag]; - [self _purgeChildren:[rootView reactSubviews] fromRegistry:viewRegistry]; - if ([rootView conformsToProtocol:@protocol(RCTInvalidating)]) { - [(id)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 *)shadowViewRegistry -{ - // NOTE: this method only exists so that it can be accessed by unit tests - if (!_shadowViewRegistry) { - _shadowViewRegistry = [NSMutableDictionary new]; - } - return _shadowViewRegistry; -} - -- (NSMutableDictionary *)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 - eventDispatcher:_bridge.eventDispatcher]; - _componentDataByName[componentData.name] = componentData; - } - } - - // Preload the a11yManager as the RCTUIManager needs it to listen for notification - // By eagerly preloading it in the setBridge method, we make sure that the manager is - // properly initialized in the Main Thread and that we do not incur in any race condition - // or concurrency problem. - id a11yManager = [bridge moduleForName:@"AccessibilityManager" lazilyLoadIfNecessary:YES]; - - // This dispatch_async avoids a deadlock while configuring native modules - dispatch_queue_t accessibilityManagerInitQueue = RCTUIManagerDispatchAccessibilityManagerInitOntoMain() - ? dispatch_get_main_queue() - : dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0); - dispatch_async(accessibilityManagerInitQueue, ^{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(didReceiveNewContentSizeMultiplier) - name:@"RCTAccessibilityManagerDidUpdateMultiplierNotification" - object:a11yManager]; - }); -#if TARGET_OS_IOS - [[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) { - [[_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"didUpdateContentSizeMultiplier" - body:multiplier]; - } -#pragma clang diagnostic pop - - RCTExecuteOnUIManagerQueue(^{ - [[NSNotificationCenter defaultCenter] - postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification - object:self]; - [self setNeedsLayout]; - }); -} - -#if TARGET_OS_IOS -// 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" - [[_moduleRegistry moduleForName:"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); - - RCTAssert( - _viewRegistry[reactTag] == nil || _viewRegistry[reactTag] == 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(); - UIView *view = [_bridge.surfacePresenter findComponentViewWithTag_DO_NOT_USE_DEPRECATED:reactTag.integerValue]; - if (!view) { - view = _viewRegistry[reactTag]; - } - return RCTPaperViewOrCurrentView(view); -} - -- (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> *)children - fromRegistry:(NSMutableDictionary> *)registry -{ - for (id child in children) { - RCTTraverseViewNodes(registry[child.reactTag], ^(id subview) { - RCTAssert(![subview isReactRootView], @"Root views should not be unregistered"); - if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) { - [(id)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(); - - NSPointerArray *affectedShadowViews = [NSPointerArray weakObjectsPointerArray]; - [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 *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; - }; -} - -/** - * 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> *)_childrenToRemoveFromContainer:(id)container - atIndices:(NSArray *)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> *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> *)children fromContainer:(id)container -{ - for (id removedChild in children) { - [container removeReactSubview:removedChild]; - } -} - -/** - * Remove subviews from their parent with an animation. - */ -- (void)_removeChildren:(NSArray *)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> *)rootShadowView.reactSubviews - fromRegistry:(NSMutableDictionary> *)_shadowViewRegistry]; - [_shadowViewRegistry removeObjectForKey:rootReactTag]; - [_rootViewTags removeObject:rootReactTag]; - - [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTAssertMainQueue(); - UIView *rootView = viewRegistry[rootReactTag]; - [uiManager _purgeChildren:(NSArray> *)rootView.reactSubviews - fromRegistry:(NSMutableDictionary> *)viewRegistry]; - [(NSMutableDictionary *)viewRegistry removeObjectForKey:rootReactTag]; - }]; -} - -RCT_EXPORT_METHOD(setChildren : (nonnull NSNumber *)containerTag reactTags : (NSArray *)reactTags) -{ - RCTSetChildren(containerTag, reactTags, (NSDictionary> *)_shadowViewRegistry); - - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTSetChildren(containerTag, reactTags, (NSDictionary> *)viewRegistry); - }]; - - [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]]; -} - -static void RCTSetChildren( - NSNumber *containerTag, - NSArray *reactTags, - NSDictionary> *registry) -{ - id container = registry[containerTag]; - NSInteger index = 0; - for (NSNumber *reactTag in reactTags) { - id view = registry[reactTag]; - if (view) { - [container insertReactSubview:view atIndex:index++]; - } - } -} - -RCT_EXPORT_METHOD( - manageChildren : (nonnull NSNumber *)containerTag moveFromIndices : (NSArray *) - moveFromIndices moveToIndices : (NSArray *)moveToIndices addChildReactTags : (NSArray *) - addChildReactTags addAtIndices : (NSArray *) - addAtIndices removeAtIndices : (NSArray *)removeAtIndices) -{ - [self _manageChildren:containerTag - moveFromIndices:moveFromIndices - moveToIndices:moveToIndices - addChildReactTags:addChildReactTags - addAtIndices:addAtIndices - removeAtIndices:removeAtIndices - registry:(NSMutableDictionary> *)_shadowViewRegistry]; - - [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - [uiManager _manageChildren:containerTag - moveFromIndices:moveFromIndices - moveToIndices:moveToIndices - addChildReactTags:addChildReactTags - addAtIndices:addAtIndices - removeAtIndices:removeAtIndices - registry:(NSMutableDictionary> *)viewRegistry]; - }]; - - [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]]; -} - -- (void)_manageChildren:(NSNumber *)containerTag - moveFromIndices:(NSArray *)moveFromIndices - moveToIndices:(NSArray *)moveToIndices - addChildReactTags:(NSArray *)addChildReactTags - addAtIndices:(NSArray *)addAtIndices - removeAtIndices:(NSArray *)removeAtIndices - registry:(NSMutableDictionary> *)registry -{ - id 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> *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container - atIndices:removeAtIndices]; - NSArray> *temporarilyRemovedChildren = [self _childrenToRemoveFromContainer:container - atIndices:moveFromIndices]; - - BOOL isUIViewRegistry = ((id)registry == (id)_viewRegistry); - if (isUIViewRegistry && _layoutAnimationGroup.deletingLayoutAnimation) { - [self _removeChildren:(NSArray *)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 view = registry[addChildReactTags[index]]; - if (view) { - destinationsToChildrenToAdd[addAtIndices[index]] = view; - } - } - - NSArray *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 *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 *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 *viewRegistry) { - UIView *newResponder = viewRegistry[reactTag]; - [newResponder reactFocus]; - }]; -} - -RCT_EXPORT_METHOD(blur : (nonnull NSNumber *)reactTag) -{ - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *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 *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 *)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 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 *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) { - RCTComposedViewRegistry *composedViewRegistry = - [[RCTComposedViewRegistry alloc] initWithUIManager:strongSelf andRegistry:strongSelf->_viewRegistry]; - block(strongSelf, composedViewRegistry); - } - } @catch (NSException *__unused 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 *)props -{ - // We collect a set with changed `shadowViews` and its changed props, - // so we have to maintain this collection properly. - NSArray *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 *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 *viewRegistry) { - for (NSNumber *tag in tags) { - UIView *view = viewRegistry[tag]; - [view didUpdateReactSubviews]; - } - }]; -} - -- (void)_dispatchPropsDidChangeEvents -{ - if (_shadowViewsWithUpdatedProps.count == 0) { - return; - } - - NSMapTable *> *shadowViews = _shadowViewsWithUpdatedProps; - _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable]; - - NSMapTable *> *tags = [NSMapTable strongToStrongObjectsMapTable]; - - for (RCTShadowView *shadowView in shadowViews) { - NSArray *props = [shadowViews objectForKey:shadowView]; - [shadowView didSetProps:props]; - [tags setObject:props forKey:shadowView.reactTag]; - } - - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - for (NSNumber *tag in tags) { - UIView *view = viewRegistry[tag]; - [view didSetProps:[tags objectForKey:tag]]; - } - }]; -} - -RCT_EXPORT_METHOD(measure : (nonnull NSNumber *)reactTag callback : (RCTResponseSenderBlock)callback) -{ - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *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 , 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 *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); -} - -/** - * 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 *viewRegistry) { - _jsResponder = viewRegistry[reactTag]; - // Fabric view's are not stored in viewRegistry. We avoid logging a warning in that case. - if (!_jsResponder && !RCTUIManagerTypeForTagIsFabric(reactTag)) { - RCTLogWarn(@"Invalid view set to be the JS responder - tag %@", reactTag); - } - }]; -} - -RCT_EXPORT_METHOD(clearJSResponder) -{ - [self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary *viewRegistry) { - _jsResponder = nil; - }]; -} - -static NSMutableDictionary *moduleConstantsForComponentData( - NSMutableDictionary *directEvents, - NSMutableDictionary *bubblingEvents, - RCTComponentData *componentData) -{ - return RCTModuleConstantsForDestructuredComponent( - directEvents, bubblingEvents, componentData.managerClass, componentData.name, componentData.viewConfig); -} - -- (NSDictionary *)constantsToExport -{ - return [self getConstants]; -} - -- (NSDictionary *)getConstants -{ - NSMutableDictionary *constants = [NSMutableDictionary new]; - NSMutableDictionary *directEvents = [NSMutableDictionary new]; - NSMutableDictionary *bubblingEvents = [NSMutableDictionary new]; - - [_componentDataByName - enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) { - RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name); - NSMutableDictionary *moduleConstants = - moduleConstantsForComponentData(directEvents, bubblingEvents, componentData); - constants[name] = moduleConstants; - }]; - - return constants; -} - -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(lazilyLoadView : (NSString *)name) -{ - if (_componentDataByName[name]) { - return @{}; - } - - id 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 - eventDispatcher:self.bridge.eventDispatcher]; - _componentDataByName[componentData.name] = componentData; - NSMutableDictionary *directEvents = [NSMutableDictionary new]; - NSMutableDictionary *bubblingEvents = [NSMutableDictionary new]; - NSMutableDictionary *moduleConstants = - moduleConstantsForComponentData(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 *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; -} - -- (void)removeViewFromRegistry:(NSNumber *)reactTag -{ - [_viewRegistry removeObjectForKey:reactTag]; -} - -@end - -@implementation RCTBridge (RCTUIManager) - -- (RCTUIManager *)uiManager -{ - return [self moduleForClass:[RCTUIManager class]]; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH - @implementation RCTUIManager - (void)registerRootViewTag:(NSNumber *)rootTag { @@ -1730,8 +235,6 @@ - (RCTUIManager *)uiManager @end -#endif // RCT_REMOVE_LEGACY_ARCH - UIView *RCTPaperViewOrCurrentView(UIView *view) { if ([view respondsToSelector:@selector(paperView)]) { diff --git a/packages/react-native/React/Views/RCTActivityIndicatorView.h b/packages/react-native/React/Views/RCTActivityIndicatorView.h index 19550f4cbd8..3f93aa3f360 100644 --- a/packages/react-native/React/Views/RCTActivityIndicatorView.h +++ b/packages/react-native/React/Views/RCTActivityIndicatorView.h @@ -6,11 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTActivityIndicatorView : UIActivityIndicatorView -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTActivityIndicatorView.m b/packages/react-native/React/Views/RCTActivityIndicatorView.m index e164fd9f3fe..a2011d692ef 100644 --- a/packages/react-native/React/Views/RCTActivityIndicatorView.m +++ b/packages/react-native/React/Views/RCTActivityIndicatorView.m @@ -6,21 +6,3 @@ */ #import "RCTActivityIndicatorView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTActivityIndicatorView { -} - -- (void)setHidden:(BOOL)hidden -{ - if ([self hidesWhenStopped] && ![self isAnimating]) { - [super setHidden:YES]; - } else { - [super setHidden:hidden]; - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTActivityIndicatorViewManager.h b/packages/react-native/React/Views/RCTActivityIndicatorViewManager.h index 79371b4f80f..b6bc9e2e3c2 100644 --- a/packages/react-native/React/Views/RCTActivityIndicatorViewManager.h +++ b/packages/react-native/React/Views/RCTActivityIndicatorViewManager.h @@ -6,18 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTConvert(UIActivityIndicatorView) - -+ (UIActivityIndicatorViewStyle)UIActivityIndicatorViewStyle:(id)json; - -@end - -@interface RCTActivityIndicatorViewManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTActivityIndicatorViewManager.m b/packages/react-native/React/Views/RCTActivityIndicatorViewManager.m index 8b9b79a0523..e4056080e52 100644 --- a/packages/react-native/React/Views/RCTActivityIndicatorViewManager.m +++ b/packages/react-native/React/Views/RCTActivityIndicatorViewManager.m @@ -6,60 +6,3 @@ */ #import "RCTActivityIndicatorViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTActivityIndicatorView.h" -#import "RCTConvert.h" - -@implementation RCTConvert (UIActivityIndicatorView) - -RCT_ENUM_CONVERTER( - UIActivityIndicatorViewStyle, - (@{ - @"large" : @(UIActivityIndicatorViewStyleLarge), - @"small" : @(UIActivityIndicatorViewStyleMedium), - }), - UIActivityIndicatorViewStyleLarge, - integerValue) - -@end - -@implementation RCTActivityIndicatorViewManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - return [RCTActivityIndicatorView new]; -} - -RCT_EXPORT_VIEW_PROPERTY(color, UIColor) -RCT_EXPORT_VIEW_PROPERTY(hidesWhenStopped, BOOL) -RCT_CUSTOM_VIEW_PROPERTY(size, UIActivityIndicatorViewStyle, UIActivityIndicatorView) -{ - /* - Setting activityIndicatorViewStyle overrides the color, so restore the original color - after setting the indicator style. - */ - UIColor *oldColor = view.color; - view.activityIndicatorViewStyle = - json ? [RCTConvert UIActivityIndicatorViewStyle:json] : defaultView.activityIndicatorViewStyle; - view.color = oldColor; -} - -RCT_CUSTOM_VIEW_PROPERTY(animating, BOOL, UIActivityIndicatorView) -{ - BOOL animating = json ? [RCTConvert BOOL:json] : [defaultView isAnimating]; - if (animating != [view isAnimating]) { - if (animating) { - [view startAnimating]; - } else { - [view stopAnimating]; - } - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTDebuggingOverlayManager.h b/packages/react-native/React/Views/RCTDebuggingOverlayManager.h index 38c1d56a13d..b6bc9e2e3c2 100644 --- a/packages/react-native/React/Views/RCTDebuggingOverlayManager.h +++ b/packages/react-native/React/Views/RCTDebuggingOverlayManager.h @@ -6,12 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTDebuggingOverlayManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTDebuggingOverlayManager.m b/packages/react-native/React/Views/RCTDebuggingOverlayManager.m index 83a85a19039..b601a53f25e 100644 --- a/packages/react-native/React/Views/RCTDebuggingOverlayManager.m +++ b/packages/react-native/React/Views/RCTDebuggingOverlayManager.m @@ -7,62 +7,3 @@ #import "RCTDebuggingOverlayManager.h" #import "RCTDebuggingOverlay.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -#import "RCTBridge.h" - -@implementation RCTDebuggingOverlayManager - -RCT_EXPORT_MODULE(DebuggingOverlay) - -- (UIView *)view -{ - return [RCTDebuggingOverlay new]; -} - -RCT_EXPORT_METHOD(highlightTraceUpdates : (nonnull NSNumber *)viewTag nodes : (NSArray *)updates) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[viewTag]; - - if ([view isKindOfClass:[RCTDebuggingOverlay class]]) { - [(RCTDebuggingOverlay *)view highlightTraceUpdates:updates]; - } else { - RCTLogError(@"Expected view to be RCTDebuggingOverlay, got %@", NSStringFromClass([view class])); - } - }]; -} - -RCT_EXPORT_METHOD(highlightElements : (nonnull NSNumber *)viewTag elements : (NSArray *)elements) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[viewTag]; - - if ([view isKindOfClass:[RCTDebuggingOverlay class]]) { - [(RCTDebuggingOverlay *)view highlightElements:elements]; - } else { - RCTLogError(@"Expected view to be RCTDebuggingOverlay, got %@", NSStringFromClass([view class])); - } - }]; -} - -RCT_EXPORT_METHOD(clearElementsHighlights : (nonnull NSNumber *)viewTag) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[viewTag]; - - if ([view isKindOfClass:[RCTDebuggingOverlay class]]) { - [(RCTDebuggingOverlay *)view clearElementsHighlights]; - } else { - RCTLogError(@"Expected view to be RCTDebuggingOverlay, got %@", NSStringFromClass([view class])); - } - }]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalHostView.h b/packages/react-native/React/Views/RCTModalHostView.h index ca2a97aa7e9..3f93aa3f360 100644 --- a/packages/react-native/React/Views/RCTModalHostView.h +++ b/packages/react-native/React/Views/RCTModalHostView.h @@ -6,59 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import - -@class RCTBridge; -@class RCTModalHostViewController; - -@protocol RCTModalHostViewInteractor; - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTModalHostView : UIView - -@property (nonatomic, copy) NSString *animationType; -@property (nonatomic, assign) UIModalPresentationStyle presentationStyle; -@property (nonatomic, assign, getter=isTransparent) BOOL transparent; - -@property (nonatomic, copy) RCTDirectEventBlock onShow; -@property (nonatomic, assign) BOOL visible; -@property (nonatomic, assign) BOOL allowSwipeDismissal; - -// Android only -@property (nonatomic, assign) BOOL statusBarTranslucent; -@property (nonatomic, assign) BOOL hardwareAccelerated; -@property (nonatomic, assign) BOOL animated; - -@property (nonatomic, copy) NSNumber *identifier; - -@property (nonatomic, weak) id delegate; - -#if !TARGET_OS_TV -@property (nonatomic, copy) NSArray *supportedOrientations; -@property (nonatomic, copy) RCTDirectEventBlock onOrientationChange; -#endif - -// Fabric only -@property (nonatomic, copy) RCTDirectEventBlock onDismiss; - -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - -@end - -@protocol RCTModalHostViewInteractor - -- (void)presentModalHostView:(RCTModalHostView *)modalHostView - withViewController:(RCTModalHostViewController *)viewController - animated:(BOOL)animated; -- (void)dismissModalHostView:(RCTModalHostView *)modalHostView - withViewController:(RCTModalHostViewController *)viewController - animated:(BOOL)animated; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalHostView.m b/packages/react-native/React/Views/RCTModalHostView.m index 3b577704b8b..0bde6f9cfc7 100644 --- a/packages/react-native/React/Views/RCTModalHostView.m +++ b/packages/react-native/React/Views/RCTModalHostView.m @@ -6,259 +6,3 @@ */ #import "RCTModalHostView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import "RCTAssert.h" -#import "RCTBridge.h" -#import "RCTModalHostViewController.h" -#import "RCTTouchHandler.h" -#import "RCTUIManager.h" -#import "RCTUtils.h" -#import "UIView+React.h" - -@implementation RCTModalHostView { - __weak RCTBridge *_bridge; - BOOL _isPresented; - RCTModalHostViewController *_modalViewController; - RCTTouchHandler *_touchHandler; - UIView *_reactSubview; -#if !TARGET_OS_TV - UIInterfaceOrientation _lastKnownOrientation; -#endif - RCTDirectEventBlock _onRequestClose; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder) - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if ((self = [super initWithFrame:CGRectZero])) { - _bridge = bridge; - _modalViewController = [RCTModalHostViewController new]; - _modalViewController.modalInPresentation = YES; - UIView *containerView = [UIView new]; - containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - _modalViewController.view = containerView; - _touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge]; - _isPresented = NO; - - __weak typeof(self) weakSelf = self; - _modalViewController.boundsDidChangeBlock = ^(CGRect newBounds) { - [weakSelf notifyForBoundsChange:newBounds]; - }; - } - - return self; -} - -- (void)setAllowSwipeDismissal:(BOOL)allowSwipeDismissal -{ - if (_allowSwipeDismissal != allowSwipeDismissal) { - _allowSwipeDismissal = allowSwipeDismissal; - _modalViewController.modalInPresentation = !allowSwipeDismissal; - } -} - -- (void)notifyForBoundsChange:(CGRect)newBounds -{ - if (_reactSubview && _isPresented) { - [_bridge.uiManager setSize:newBounds.size forView:_reactSubview]; - [self notifyForOrientationChange]; - } -} - -- (void)setOnRequestClose:(RCTDirectEventBlock)onRequestClose -{ - _onRequestClose = onRequestClose; -} - -- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)controller -{ - if (_onRequestClose != nil) { - _onRequestClose(nil); - } -} - -- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController -{ - if (_onRequestClose != nil && _allowSwipeDismissal) { - _onRequestClose(nil); - } -} - -- (void)notifyForOrientationChange -{ -#if !TARGET_OS_TV - if (!_onOrientationChange) { - return; - } - - UIInterfaceOrientation currentOrientation = RCTKeyWindow().windowScene.interfaceOrientation; - if (currentOrientation == _lastKnownOrientation) { - return; - } - _lastKnownOrientation = currentOrientation; - - BOOL isPortrait = currentOrientation == UIInterfaceOrientationPortrait || - currentOrientation == UIInterfaceOrientationPortraitUpsideDown; - NSDictionary *eventPayload = @{ - @"orientation" : isPortrait ? @"portrait" : @"landscape", - }; - _onOrientationChange(eventPayload); -#endif -} - -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex -{ - RCTAssert(_reactSubview == nil, @"Modal view can only have one subview"); - [super insertReactSubview:subview atIndex:atIndex]; - [_touchHandler attachToView:subview]; - - [_modalViewController.view insertSubview:subview atIndex:0]; - _reactSubview = subview; -} - -- (void)removeReactSubview:(UIView *)subview -{ - RCTAssert(subview == _reactSubview, @"Cannot remove view other than modal view"); - // Superclass (category) removes the `subview` from actual `superview`. - [super removeReactSubview:subview]; - [_touchHandler detachFromView:subview]; - _reactSubview = nil; -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as subview (singular) is managed by `insertReactSubview:atIndex:` -} - -- (void)dismissModalViewController -{ - if (_isPresented) { - [_delegate dismissModalHostView:self withViewController:_modalViewController animated:[self hasAnimationType]]; - _isPresented = NO; - [self setVisible:NO]; - } -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - - // In the case where there is a LayoutAnimation, we will be reinserted into the view hierarchy but only for aesthetic - // purposes. In such a case, we should NOT represent the . - if (!self.userInteractionEnabled && ![self.superview.reactSubviews containsObject:self]) { - return; - } - - [self ensurePresentedOnlyIfNeeded]; -} - -- (void)didMoveToSuperview -{ - [super didMoveToSuperview]; - [self ensurePresentedOnlyIfNeeded]; -} - -- (void)invalidate -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self dismissModalViewController]; - }); -} - -- (BOOL)isTransparent -{ - return _modalViewController.modalPresentationStyle == UIModalPresentationOverFullScreen; -} - -- (BOOL)hasAnimationType -{ - return ![self.animationType isEqualToString:@"none"]; -} - -- (void)setVisible:(BOOL)visible -{ - if (_visible != visible) { - _visible = visible; - [self ensurePresentedOnlyIfNeeded]; - } -} - -- (void)ensurePresentedOnlyIfNeeded -{ - BOOL shouldBePresented = !_isPresented && _visible && self.window; - if (shouldBePresented) { - RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller"); - -#if !TARGET_OS_TV - _modalViewController.supportedInterfaceOrientations = [self supportedOrientationsMask]; -#endif - - if ([self.animationType isEqualToString:@"fade"]) { - _modalViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - } else if ([self.animationType isEqualToString:@"slide"]) { - _modalViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; - } - if (self.presentationStyle != UIModalPresentationNone) { - _modalViewController.modalPresentationStyle = self.presentationStyle; - } - - _modalViewController.presentationController.delegate = self; - - [_delegate presentModalHostView:self withViewController:_modalViewController animated:[self hasAnimationType]]; - _isPresented = YES; - } - - BOOL shouldBeHidden = _isPresented && (!_visible || !self.superview); - if (shouldBeHidden) { - [self dismissModalViewController]; - } -} - -- (void)setTransparent:(BOOL)transparent -{ - if (self.isTransparent != transparent) { - return; - } - - _modalViewController.modalPresentationStyle = - transparent ? UIModalPresentationOverFullScreen : UIModalPresentationFullScreen; -} - -#if !TARGET_OS_TV -- (UIInterfaceOrientationMask)supportedOrientationsMask -{ - if (_supportedOrientations.count == 0) { - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { - return UIInterfaceOrientationMaskAll; - } else { - return UIInterfaceOrientationMaskPortrait; - } - } - - UIInterfaceOrientationMask supportedOrientations = 0; - for (NSString *orientation in _supportedOrientations) { - if ([orientation isEqualToString:@"portrait"]) { - supportedOrientations |= UIInterfaceOrientationMaskPortrait; - } else if ([orientation isEqualToString:@"portrait-upside-down"]) { - supportedOrientations |= UIInterfaceOrientationMaskPortraitUpsideDown; - } else if ([orientation isEqualToString:@"landscape"]) { - supportedOrientations |= UIInterfaceOrientationMaskLandscape; - } else if ([orientation isEqualToString:@"landscape-left"]) { - supportedOrientations |= UIInterfaceOrientationMaskLandscapeLeft; - } else if ([orientation isEqualToString:@"landscape-right"]) { - supportedOrientations |= UIInterfaceOrientationMaskLandscapeRight; - } - } - return supportedOrientations; -} -#endif - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalHostViewController.h b/packages/react-native/React/Views/RCTModalHostViewController.h index 80b075045e5..3f93aa3f360 100644 --- a/packages/react-native/React/Views/RCTModalHostViewController.h +++ b/packages/react-native/React/Views/RCTModalHostViewController.h @@ -6,18 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTModalHostViewController : UIViewController - -@property (nonatomic, copy) void (^boundsDidChangeBlock)(CGRect newBounds); - -#if !TARGET_OS_TV -@property (nonatomic, assign) UIInterfaceOrientationMask supportedInterfaceOrientations; -#endif - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalHostViewController.m b/packages/react-native/React/Views/RCTModalHostViewController.m index ecb8f10f641..8eec2cb2669 100644 --- a/packages/react-native/React/Views/RCTModalHostViewController.m +++ b/packages/react-native/React/Views/RCTModalHostViewController.m @@ -6,69 +6,3 @@ */ #import "RCTModalHostViewController.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTLog.h" -#import "RCTModalHostView.h" - -@implementation RCTModalHostViewController { - CGRect _lastViewFrame; - UIStatusBarStyle _preferredStatusBarStyle; - BOOL _preferredStatusBarHidden; -} - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _preferredStatusBarStyle = [RCTUIStatusBarManager() statusBarStyle]; - _preferredStatusBarHidden = [RCTUIStatusBarManager() isStatusBarHidden]; - - return self; -} - -- (void)viewDidLayoutSubviews -{ - [super viewDidLayoutSubviews]; - - if (self.boundsDidChangeBlock && !CGRectEqualToRect(_lastViewFrame, self.view.frame)) { - self.boundsDidChangeBlock(self.view.bounds); - _lastViewFrame = self.view.frame; - } -} - -- (UIStatusBarStyle)preferredStatusBarStyle -{ - return _preferredStatusBarStyle; -} - -- (BOOL)prefersStatusBarHidden -{ - return _preferredStatusBarHidden; -} - -#if RCT_DEV && TARGET_OS_IOS -- (UIInterfaceOrientationMask)supportedInterfaceOrientations -{ - UIInterfaceOrientationMask appSupportedOrientationsMask = - [RCTSharedApplication() supportedInterfaceOrientationsForWindow:RCTKeyWindow()]; - if (!(_supportedInterfaceOrientations & appSupportedOrientationsMask)) { - RCTLogError( - @"Modal was presented with 0x%x orientations mask but the application only supports 0x%x." - @"Add more interface orientations to your app's Info.plist to fix this." - @"NOTE: This will crash in non-dev mode.", - (unsigned)_supportedInterfaceOrientations, - (unsigned)appSupportedOrientationsMask); - return UIInterfaceOrientationMaskAll; - } - - return _supportedInterfaceOrientations; -} -#endif // RCT_DEV - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalHostViewManager.h b/packages/react-native/React/Views/RCTModalHostViewManager.h index d0b3d9782c4..0e68ebf015e 100644 --- a/packages/react-native/React/Views/RCTModalHostViewManager.h +++ b/packages/react-native/React/Views/RCTModalHostViewManager.h @@ -8,27 +8,3 @@ #import #import #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -typedef void (^RCTModalViewInteractionBlock)( - UIViewController *reactViewController, - UIViewController *viewController, - BOOL animated, - dispatch_block_t completionBlock) - __attribute__((deprecated("This API will be removed along with the legacy architecture."))); - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTModalHostViewManager : RCTViewManager - -/** - * `presentationBlock` and `dismissalBlock` allow you to control how a Modal interacts with your case, - * e.g. in case you have a native navigator that has its own way to display a modal. - * If these are not specified, it falls back to the UIViewController standard way of presenting. - */ -@property (nonatomic, strong) RCTModalViewInteractionBlock presentationBlock; -@property (nonatomic, strong) RCTModalViewInteractionBlock dismissalBlock; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalHostViewManager.m b/packages/react-native/React/Views/RCTModalHostViewManager.m index 147cd412439..45d8971f0db 100644 --- a/packages/react-native/React/Views/RCTModalHostViewManager.m +++ b/packages/react-native/React/Views/RCTModalHostViewManager.m @@ -6,126 +6,3 @@ */ #import "RCTModalHostViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTBridge.h" -#import "RCTModalHostView.h" -#import "RCTModalHostViewController.h" -#import "RCTModalManager.h" -#import "RCTShadowView.h" -#import "RCTUtils.h" - -@interface RCTModalHostShadowView : RCTShadowView - -@end - -@implementation RCTModalHostShadowView - -- (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:subview atIndex:atIndex]; - if ([subview isKindOfClass:[RCTShadowView class]]) { - ((RCTShadowView *)subview).size = RCTScreenSize(); - } -} - -@end - -@interface RCTModalHostViewManager () - -@end - -@implementation RCTModalHostViewManager { - NSPointerArray *_hostViews; -} - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - RCTModalHostView *view = [[RCTModalHostView alloc] initWithBridge:self.bridge]; - view.delegate = self; - if (!_hostViews) { - _hostViews = [NSPointerArray weakObjectsPointerArray]; - } - [_hostViews addPointer:(__bridge void *)view]; - return view; -} - -- (void)presentModalHostView:(RCTModalHostView *)modalHostView - withViewController:(RCTModalHostViewController *)viewController - animated:(BOOL)animated -{ - dispatch_block_t completionBlock = ^{ - if (modalHostView.onShow) { - modalHostView.onShow(nil); - } - }; - dispatch_async(dispatch_get_main_queue(), ^{ - if (self->_presentationBlock) { - self->_presentationBlock([modalHostView reactViewController], viewController, animated, completionBlock); - } else { - [[modalHostView reactViewController] presentViewController:viewController - animated:animated - completion:completionBlock]; - } - }); -} - -- (void)dismissModalHostView:(RCTModalHostView *)modalHostView - withViewController:(RCTModalHostViewController *)viewController - animated:(BOOL)animated -{ - dispatch_block_t completionBlock = ^{ - if (modalHostView.identifier) { - [[self.bridge moduleForClass:[RCTModalManager class]] modalDismissed:modalHostView.identifier]; - } - }; - dispatch_async(dispatch_get_main_queue(), ^{ - if (self->_dismissalBlock) { - self->_dismissalBlock([modalHostView reactViewController], viewController, animated, completionBlock); - } else if (viewController.presentingViewController) { - [viewController.presentingViewController dismissViewControllerAnimated:animated completion:completionBlock]; - } else { - // Make sure to call the completion block in case the presenting view controller is nil - // In an internal app we have a use case where a modal presents another view without bein dismissed - // This, somehow, invalidate the presenting view controller and the modal remains always visible. - completionBlock(); - } - }); -} - -- (RCTShadowView *)shadowView -{ - return [RCTModalHostShadowView new]; -} - -- (void)invalidate -{ - for (RCTModalHostView *hostView in _hostViews) { - [hostView invalidate]; - } - _hostViews = nil; -} - -RCT_EXPORT_VIEW_PROPERTY(animationType, NSString) -RCT_EXPORT_VIEW_PROPERTY(presentationStyle, UIModalPresentationStyle) -RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL) -RCT_EXPORT_VIEW_PROPERTY(statusBarTranslucent, BOOL) -RCT_EXPORT_VIEW_PROPERTY(hardwareAccelerated, BOOL) -RCT_EXPORT_VIEW_PROPERTY(animated, BOOL) -RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(identifier, NSNumber) -RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray) -RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(visible, BOOL) -RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(allowSwipeDismissal, BOOL) - -// Fabric only -RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock) - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalManager.h b/packages/react-native/React/Views/RCTModalManager.h index f00b4b2957f..3f93aa3f360 100644 --- a/packages/react-native/React/Views/RCTModalManager.h +++ b/packages/react-native/React/Views/RCTModalManager.h @@ -6,17 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTModalManager : RCTEventEmitter - -- (void)modalDismissed:(NSNumber *)modalID; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTModalManager.m b/packages/react-native/React/Views/RCTModalManager.m index 6e15eb83416..e2cac7eeacc 100644 --- a/packages/react-native/React/Views/RCTModalManager.m +++ b/packages/react-native/React/Views/RCTModalManager.m @@ -6,41 +6,3 @@ */ #import "RCTModalManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@interface RCTModalManager () - -@property BOOL shouldEmit; - -@end - -@implementation RCTModalManager - -RCT_EXPORT_MODULE(); - -- (NSArray *)supportedEvents -{ - return @[ @"modalDismissed" ]; -} - -- (void)startObserving -{ - _shouldEmit = YES; -} - -- (void)stopObserving -{ - _shouldEmit = NO; -} - -- (void)modalDismissed:(NSNumber *)modalID -{ - if (_shouldEmit) { - [self sendEventWithName:@"modalDismissed" body:@{@"modalID" : modalID}]; - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTRootShadowView.m b/packages/react-native/React/Views/RCTRootShadowView.m index c5e5d831b5b..8131d827610 100644 --- a/packages/react-native/React/Views/RCTRootShadowView.m +++ b/packages/react-native/React/Views/RCTRootShadowView.m @@ -7,47 +7,8 @@ #import "RCTRootShadowView.h" -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTI18nUtil.h" -#import "RCTShadowView+Layout.h" - -@implementation RCTRootShadowView - -- (instancetype)init -{ - if (self = [super init]) { - _baseDirection = [[RCTI18nUtil sharedInstance] isRTL] ? YGDirectionRTL : YGDirectionLTR; - _minimumSize = CGSizeZero; - _availableSize = CGSizeMake(INFINITY, INFINITY); - } - - return self; -} - -- (void)layoutWithAffectedShadowViews:(NSPointerArray *)affectedShadowViews -{ - NSHashTable *other = [NSHashTable new]; - - RCTLayoutContext layoutContext = {}; - layoutContext.absolutePosition = CGPointZero; - layoutContext.affectedShadowViews = affectedShadowViews; - layoutContext.other = other; - - [self layoutWithMinimumSize:_minimumSize - maximumSize:_availableSize - layoutDirection:RCTUIKitLayoutDirectionFromYogaLayoutDirection(_baseDirection) - layoutContext:layoutContext]; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH - @implementation RCTRootShadowView - (void)layoutWithAffectedShadowViews:(NSPointerArray *)affectedShadowViews { } @end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTShadowView.m b/packages/react-native/React/Views/RCTShadowView.m index a7a3d394848..10d199520f4 100644 --- a/packages/react-native/React/Views/RCTShadowView.m +++ b/packages/react-native/React/Views/RCTShadowView.m @@ -16,728 +16,6 @@ #import "UIView+Private.h" #import "UIView+React.h" -#ifndef RCT_REMOVE_LEGACY_ARCH - -typedef void (^RCTActionBlock)(RCTShadowView *shadowViewSelf, id value); -typedef void (^RCTResetActionBlock)(RCTShadowView *shadowViewSelf); - -typedef NS_ENUM(unsigned int, meta_prop_t) { - META_PROP_LEFT, - META_PROP_TOP, - META_PROP_RIGHT, - META_PROP_BOTTOM, - META_PROP_START, - META_PROP_END, - META_PROP_HORIZONTAL, - META_PROP_VERTICAL, - META_PROP_ALL, - META_PROP_COUNT, -}; - -@implementation RCTShadowView { - NSDictionary *_lastParentProperties; - NSMutableArray *_reactSubviews; - BOOL _recomputePadding; - BOOL _recomputeMargin; - BOOL _recomputeBorder; - YGValue _paddingMetaProps[META_PROP_COUNT]; - YGValue _marginMetaProps[META_PROP_COUNT]; - YGValue _borderMetaProps[META_PROP_COUNT]; -} - -+ (YGConfigRef)yogaConfig -{ - static YGConfigRef yogaConfig; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - yogaConfig = YGConfigNew(); - YGConfigSetPointScaleFactor(yogaConfig, RCTScreenScale()); - YGConfigSetErrata(yogaConfig, YGErrataAll); - }); - return yogaConfig; -} - -@synthesize reactTag = _reactTag; -@synthesize rootTag = _rootTag; - -// YogaNode API - -#define RCT_SET_YGVALUE(ygvalue, setter, ...) \ - switch (ygvalue.unit) { \ - case YGUnitAuto: \ - case YGUnitMaxContent: \ - case YGUnitFitContent: \ - case YGUnitStretch: \ - case YGUnitUndefined: \ - setter(__VA_ARGS__, YGUndefined); \ - break; \ - case YGUnitPoint: \ - setter(__VA_ARGS__, ygvalue.value); \ - break; \ - case YGUnitPercent: \ - setter##Percent(__VA_ARGS__, ygvalue.value); \ - break; \ - } - -#define RCT_SET_YGVALUE_AUTO(ygvalue, setter, ...) \ - switch (ygvalue.unit) { \ - case YGUnitAuto: \ - setter##Auto(__VA_ARGS__); \ - break; \ - case YGUnitUndefined: \ - setter(__VA_ARGS__, YGUndefined); \ - break; \ - case YGUnitPoint: \ - setter(__VA_ARGS__, ygvalue.value); \ - break; \ - case YGUnitPercent: \ - setter##Percent(__VA_ARGS__, ygvalue.value); \ - break; \ - case YGUnitMaxContent: \ - case YGUnitFitContent: \ - case YGUnitStretch: \ - break; \ - } - -#define RCT_SET_YGVALUE_AUTO_INTRINSIC(ygvalue, setter, ...) \ - switch (ygvalue.unit) { \ - case YGUnitAuto: \ - setter##Auto(__VA_ARGS__); \ - break; \ - case YGUnitMaxContent: \ - setter##MaxContent(__VA_ARGS__); \ - break; \ - case YGUnitFitContent: \ - setter##FitContent(__VA_ARGS__); \ - break; \ - case YGUnitStretch: \ - setter##Stretch(__VA_ARGS__); \ - break; \ - case YGUnitUndefined: \ - setter(__VA_ARGS__, YGUndefined); \ - break; \ - case YGUnitPoint: \ - setter(__VA_ARGS__, ygvalue.value); \ - break; \ - case YGUnitPercent: \ - setter##Percent(__VA_ARGS__, ygvalue.value); \ - break; \ - } - -static void RCTProcessMetaPropsPadding(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) -{ - if (![[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]) { - RCT_SET_YGVALUE(metaProps[META_PROP_START], YGNodeStyleSetPadding, node, YGEdgeStart); - RCT_SET_YGVALUE(metaProps[META_PROP_END], YGNodeStyleSetPadding, node, YGEdgeEnd); - RCT_SET_YGVALUE(metaProps[META_PROP_LEFT], YGNodeStyleSetPadding, node, YGEdgeLeft); - RCT_SET_YGVALUE(metaProps[META_PROP_RIGHT], YGNodeStyleSetPadding, node, YGEdgeRight); - } else { - YGValue start = - metaProps[META_PROP_START].unit == YGUnitUndefined ? metaProps[META_PROP_LEFT] : metaProps[META_PROP_START]; - YGValue end = - metaProps[META_PROP_END].unit == YGUnitUndefined ? metaProps[META_PROP_RIGHT] : metaProps[META_PROP_END]; - RCT_SET_YGVALUE(start, YGNodeStyleSetPadding, node, YGEdgeStart); - RCT_SET_YGVALUE(end, YGNodeStyleSetPadding, node, YGEdgeEnd); - } - RCT_SET_YGVALUE(metaProps[META_PROP_TOP], YGNodeStyleSetPadding, node, YGEdgeTop); - RCT_SET_YGVALUE(metaProps[META_PROP_BOTTOM], YGNodeStyleSetPadding, node, YGEdgeBottom); - RCT_SET_YGVALUE(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSetPadding, node, YGEdgeHorizontal); - RCT_SET_YGVALUE(metaProps[META_PROP_VERTICAL], YGNodeStyleSetPadding, node, YGEdgeVertical); - RCT_SET_YGVALUE(metaProps[META_PROP_ALL], YGNodeStyleSetPadding, node, YGEdgeAll); -} - -static void RCTProcessMetaPropsMargin(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) -{ - if (![[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]) { - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_START], YGNodeStyleSetMargin, node, YGEdgeStart); - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_END], YGNodeStyleSetMargin, node, YGEdgeEnd); - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_LEFT], YGNodeStyleSetMargin, node, YGEdgeLeft); - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_RIGHT], YGNodeStyleSetMargin, node, YGEdgeRight); - } else { - YGValue start = - metaProps[META_PROP_START].unit == YGUnitUndefined ? metaProps[META_PROP_LEFT] : metaProps[META_PROP_START]; - YGValue end = - metaProps[META_PROP_END].unit == YGUnitUndefined ? metaProps[META_PROP_RIGHT] : metaProps[META_PROP_END]; - RCT_SET_YGVALUE_AUTO(start, YGNodeStyleSetMargin, node, YGEdgeStart); - RCT_SET_YGVALUE_AUTO(end, YGNodeStyleSetMargin, node, YGEdgeEnd); - } - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_TOP], YGNodeStyleSetMargin, node, YGEdgeTop); - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_BOTTOM], YGNodeStyleSetMargin, node, YGEdgeBottom); - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSetMargin, node, YGEdgeHorizontal); - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_VERTICAL], YGNodeStyleSetMargin, node, YGEdgeVertical); - RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_ALL], YGNodeStyleSetMargin, node, YGEdgeAll); -} - -static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) -{ - if (![[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]) { - YGNodeStyleSetBorder(node, YGEdgeStart, metaProps[META_PROP_START].value); - YGNodeStyleSetBorder(node, YGEdgeEnd, metaProps[META_PROP_END].value); - YGNodeStyleSetBorder(node, YGEdgeLeft, metaProps[META_PROP_LEFT].value); - YGNodeStyleSetBorder(node, YGEdgeRight, metaProps[META_PROP_RIGHT].value); - } else { - const float start = YGFloatIsUndefined(metaProps[META_PROP_START].value) ? metaProps[META_PROP_LEFT].value - : metaProps[META_PROP_START].value; - const float end = YGFloatIsUndefined(metaProps[META_PROP_END].value) ? metaProps[META_PROP_RIGHT].value - : metaProps[META_PROP_END].value; - YGNodeStyleSetBorder(node, YGEdgeStart, start); - YGNodeStyleSetBorder(node, YGEdgeEnd, end); - } - YGNodeStyleSetBorder(node, YGEdgeTop, metaProps[META_PROP_TOP].value); - YGNodeStyleSetBorder(node, YGEdgeBottom, metaProps[META_PROP_BOTTOM].value); - YGNodeStyleSetBorder(node, YGEdgeHorizontal, metaProps[META_PROP_HORIZONTAL].value); - YGNodeStyleSetBorder(node, YGEdgeVertical, metaProps[META_PROP_VERTICAL].value); - YGNodeStyleSetBorder(node, YGEdgeAll, metaProps[META_PROP_ALL].value); -} - -- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor -{ - CGPoint offset = CGPointZero; - RCTShadowView *shadowView = self; - while (shadowView && shadowView != ancestor) { - offset.x += shadowView.layoutMetrics.frame.origin.x; - offset.y += shadowView.layoutMetrics.frame.origin.y; - shadowView = shadowView->_superview; - } - if (ancestor != shadowView) { - return CGRectNull; - } - return (CGRect){offset, self.layoutMetrics.frame.size}; -} - -- (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor -{ - RCTShadowView *shadowView = self; - while (shadowView && shadowView != ancestor) { - shadowView = shadowView->_superview; - } - return ancestor == shadowView; -} - -- (instancetype)init -{ - if (self = [super init]) { - for (unsigned int ii = 0; ii < META_PROP_COUNT; ii++) { - _paddingMetaProps[ii] = YGValueUndefined; - _marginMetaProps[ii] = YGValueUndefined; - _borderMetaProps[ii] = YGValueUndefined; - } - - _intrinsicContentSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric); - - _newView = YES; - - _reactSubviews = [NSMutableArray array]; - - _yogaNode = YGNodeNewWithConfig([[self class] yogaConfig]); - YGNodeSetContext(_yogaNode, (__bridge void *)self); - } - return self; -} - -- (BOOL)isReactRootView -{ - return RCTIsReactRootView(self.reactTag); -} - -- (void)dealloc -{ - YGNodeFree(_yogaNode); -} - -- (BOOL)canHaveSubviews -{ - return YES; -} - -- (BOOL)isYogaLeafNode -{ - return NO; -} - -- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex -{ - RCTAssert(self.canHaveSubviews, @"Attempt to insert subview inside leaf view."); - - [_reactSubviews insertObject:subview atIndex:atIndex]; - if (![self isYogaLeafNode]) { - YGNodeInsertChild(_yogaNode, subview.yogaNode, (uint32_t)atIndex); - } - subview->_superview = self; -} - -- (void)removeReactSubview:(RCTShadowView *)subview -{ - subview->_superview = nil; - [_reactSubviews removeObject:subview]; - if (![self isYogaLeafNode]) { - YGNodeRemoveChild(_yogaNode, subview.yogaNode); - } -} - -- (NSArray *)reactSubviews -{ - return _reactSubviews; -} - -- (RCTShadowView *)reactSuperview -{ - return _superview; -} - -#pragma mark - Layout - -- (void)layoutWithMinimumSize:(CGSize)minimumSize - maximumSize:(CGSize)maximumSize - layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection - layoutContext:(RCTLayoutContext)layoutContext -{ - YGNodeRef yogaNode = _yogaNode; - - CGSize oldMinimumSize = (CGSize){RCTCoreGraphicsFloatFromYogaValue(YGNodeStyleGetMinWidth(yogaNode), 0.0), - RCTCoreGraphicsFloatFromYogaValue(YGNodeStyleGetMinHeight(yogaNode), 0.0)}; - - if (!CGSizeEqualToSize(oldMinimumSize, minimumSize)) { - YGNodeStyleSetMinWidth(yogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.width)); - YGNodeStyleSetMinHeight(yogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.height)); - } - - YGNodeCalculateLayout( - yogaNode, - RCTYogaFloatFromCoreGraphicsFloat(maximumSize.width), - RCTYogaFloatFromCoreGraphicsFloat(maximumSize.height), - RCTYogaLayoutDirectionFromUIKitLayoutDirection(layoutDirection)); - - RCTAssert(!YGNodeIsDirty(yogaNode), @"Attempt to get layout metrics from dirtied Yoga node."); - - if (!YGNodeGetHasNewLayout(yogaNode)) { - return; - } - - YGNodeSetHasNewLayout(yogaNode, false); - - RCTLayoutMetrics layoutMetrics = RCTLayoutMetricsFromYogaNode(yogaNode); - - layoutContext.absolutePosition.x += layoutMetrics.frame.origin.x; - layoutContext.absolutePosition.y += layoutMetrics.frame.origin.y; - - [self layoutWithMetrics:layoutMetrics layoutContext:layoutContext]; - - [self layoutSubviewsWithContext:layoutContext]; -} - -- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics layoutContext:(RCTLayoutContext)layoutContext -{ - if (!RCTLayoutMetricsEqualToLayoutMetrics(self.layoutMetrics, layoutMetrics)) { - self.layoutMetrics = layoutMetrics; - [layoutContext.affectedShadowViews addPointer:((__bridge void *)self)]; - } -} - -- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext -{ - RCTLayoutMetrics layoutMetrics = self.layoutMetrics; - - if (layoutMetrics.displayType == RCTDisplayTypeNone) { - return; - } - - for (RCTShadowView *childShadowView in _reactSubviews) { - YGNodeRef childYogaNode = childShadowView.yogaNode; - - RCTAssert(!YGNodeIsDirty(childYogaNode), @"Attempt to get layout metrics from dirtied Yoga node."); - - if (!YGNodeGetHasNewLayout(childYogaNode)) { - continue; - } - - YGNodeSetHasNewLayout(childYogaNode, false); - - RCTLayoutMetrics childLayoutMetrics = RCTLayoutMetricsFromYogaNode(childYogaNode); - - layoutContext.absolutePosition.x += childLayoutMetrics.frame.origin.x; - layoutContext.absolutePosition.y += childLayoutMetrics.frame.origin.y; - - [childShadowView layoutWithMetrics:childLayoutMetrics layoutContext:layoutContext]; - - // Recursive call. - [childShadowView layoutSubviewsWithContext:layoutContext]; - } -} - -- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize -{ - YGNodeRef clonedYogaNode = YGNodeClone(self.yogaNode); - YGNodeRef constraintYogaNode = YGNodeNewWithConfig([[self class] yogaConfig]); - - YGNodeInsertChild(constraintYogaNode, clonedYogaNode, 0); - - YGNodeStyleSetMinWidth(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.width)); - YGNodeStyleSetMinHeight(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.height)); - YGNodeStyleSetMaxWidth(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(maximumSize.width)); - YGNodeStyleSetMaxHeight(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(maximumSize.height)); - - YGNodeCalculateLayout( - constraintYogaNode, - YGUndefined, - YGUndefined, - RCTYogaLayoutDirectionFromUIKitLayoutDirection(self.layoutMetrics.layoutDirection)); - - CGSize measuredSize = (CGSize){ - RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetWidth(constraintYogaNode)), - RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetHeight(constraintYogaNode)), - }; - - YGNodeRemoveChild(constraintYogaNode, clonedYogaNode); - YGNodeFree(constraintYogaNode); - YGNodeFree(clonedYogaNode); - - // `setOwner()` for children unlinked by `YGNodeFree()` - int childCount = YGNodeGetChildCount(self.yogaNode); - for (int i = 0; i < childCount; i++) { - YGNodeRef child = YGNodeGetChild(self.yogaNode, i); - YGNodeSwapChild(self.yogaNode, child, i); - } - - return measuredSize; -} - -- (NSNumber *)reactTagAtPoint:(CGPoint)point -{ - for (RCTShadowView *shadowView in _reactSubviews) { - if (CGRectContainsPoint(shadowView.layoutMetrics.frame, point)) { - CGPoint relativePoint = point; - CGPoint origin = shadowView.layoutMetrics.frame.origin; - relativePoint.x -= origin.x; - relativePoint.y -= origin.y; - return [shadowView reactTagAtPoint:relativePoint]; - } - } - return self.reactTag; -} - -- (NSString *)description -{ - NSString *description = super.description; - description = [[description substringToIndex:description.length - 1] - stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", - self.viewName, - self.reactTag, - NSStringFromCGRect(self.layoutMetrics.frame)]; - return description; -} - -- (void)addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level -{ - for (NSUInteger i = 0; i < level; i++) { - [string appendString:@" | "]; - } - - [string appendString:self.description]; - [string appendString:@"\n"]; - - for (RCTShadowView *subview in _reactSubviews) { - [subview addRecursiveDescriptionToString:string atLevel:level + 1]; - } -} - -- (NSString *)recursiveDescription -{ - NSMutableString *description = [NSMutableString string]; - [self addRecursiveDescriptionToString:description atLevel:0]; - return description; -} - -// Margin - -#define RCT_MARGIN_PROPERTY(prop, metaProp) \ - -(void)setMargin##prop : (YGValue)value \ - { \ - _marginMetaProps[META_PROP_##metaProp] = value; \ - _recomputeMargin = YES; \ - } \ - -(YGValue)margin##prop \ - { \ - return _marginMetaProps[META_PROP_##metaProp]; \ - } - -RCT_MARGIN_PROPERTY(, ALL) -RCT_MARGIN_PROPERTY(Vertical, VERTICAL) -RCT_MARGIN_PROPERTY(Horizontal, HORIZONTAL) -RCT_MARGIN_PROPERTY(Top, TOP) -RCT_MARGIN_PROPERTY(Left, LEFT) -RCT_MARGIN_PROPERTY(Bottom, BOTTOM) -RCT_MARGIN_PROPERTY(Right, RIGHT) -RCT_MARGIN_PROPERTY(Start, START) -RCT_MARGIN_PROPERTY(End, END) - -// Padding - -#define RCT_PADDING_PROPERTY(prop, metaProp) \ - -(void)setPadding##prop : (YGValue)value \ - { \ - _paddingMetaProps[META_PROP_##metaProp] = value; \ - _recomputePadding = YES; \ - } \ - -(YGValue)padding##prop \ - { \ - return _paddingMetaProps[META_PROP_##metaProp]; \ - } - -RCT_PADDING_PROPERTY(, ALL) -RCT_PADDING_PROPERTY(Vertical, VERTICAL) -RCT_PADDING_PROPERTY(Horizontal, HORIZONTAL) -RCT_PADDING_PROPERTY(Top, TOP) -RCT_PADDING_PROPERTY(Left, LEFT) -RCT_PADDING_PROPERTY(Bottom, BOTTOM) -RCT_PADDING_PROPERTY(Right, RIGHT) -RCT_PADDING_PROPERTY(Start, START) -RCT_PADDING_PROPERTY(End, END) - -// Border - -#define RCT_BORDER_PROPERTY(prop, metaProp) \ - -(void)setBorder##prop##Width : (float)value \ - { \ - _borderMetaProps[META_PROP_##metaProp].value = value; \ - _recomputeBorder = YES; \ - } \ - -(float)border##prop##Width \ - { \ - return _borderMetaProps[META_PROP_##metaProp].value; \ - } - -RCT_BORDER_PROPERTY(, ALL) -RCT_BORDER_PROPERTY(Top, TOP) -RCT_BORDER_PROPERTY(Left, LEFT) -RCT_BORDER_PROPERTY(Bottom, BOTTOM) -RCT_BORDER_PROPERTY(Right, RIGHT) -RCT_BORDER_PROPERTY(Start, START) -RCT_BORDER_PROPERTY(End, END) - -// Dimensions -#define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \ - -(void)set##setProp : (YGValue)value \ - { \ - RCT_SET_YGVALUE_AUTO_INTRINSIC(value, YGNodeStyleSet##cssProp, _yogaNode); \ - } \ - -(YGValue)getProp \ - { \ - return YGNodeStyleGet##cssProp(_yogaNode); \ - } - -#define RCT_MIN_MAX_DIMENSION_PROPERTY(setProp, getProp, cssProp) \ - -(void)set##setProp : (YGValue)value \ - { \ - RCT_SET_YGVALUE(value, YGNodeStyleSet##cssProp, _yogaNode); \ - } \ - -(YGValue)getProp \ - { \ - return YGNodeStyleGet##cssProp(_yogaNode); \ - } - -RCT_DIMENSION_PROPERTY(Width, width, Width) -RCT_DIMENSION_PROPERTY(Height, height, Height) -RCT_MIN_MAX_DIMENSION_PROPERTY(MinWidth, minWidth, MinWidth) -RCT_MIN_MAX_DIMENSION_PROPERTY(MinHeight, minHeight, MinHeight) -RCT_MIN_MAX_DIMENSION_PROPERTY(MaxWidth, maxWidth, MaxWidth) -RCT_MIN_MAX_DIMENSION_PROPERTY(MaxHeight, maxHeight, MaxHeight) - -// Position - -#define RCT_POSITION_PROPERTY(setProp, getProp, edge) \ - -(void)set##setProp : (YGValue)value \ - { \ - RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge); \ - } \ - -(YGValue)getProp \ - { \ - return YGNodeStyleGetPosition(_yogaNode, edge); \ - } - -RCT_POSITION_PROPERTY(Top, top, YGEdgeTop) -RCT_POSITION_PROPERTY(Bottom, bottom, YGEdgeBottom) -RCT_POSITION_PROPERTY(Start, start, YGEdgeStart) -RCT_POSITION_PROPERTY(End, end, YGEdgeEnd) - -- (void)setLeft:(YGValue)value -{ - YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeStart : YGEdgeLeft; - RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge); -} -- (YGValue)left -{ - YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeStart : YGEdgeLeft; - return YGNodeStyleGetPosition(_yogaNode, edge); -} - -- (void)setRight:(YGValue)value -{ - YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeEnd : YGEdgeRight; - RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge); -} -- (YGValue)right -{ - YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeEnd : YGEdgeRight; - return YGNodeStyleGetPosition(_yogaNode, edge); -} - -// Size - -- (CGSize)size -{ - YGValue width = YGNodeStyleGetWidth(_yogaNode); - YGValue height = YGNodeStyleGetHeight(_yogaNode); - - return CGSizeMake(width.unit == YGUnitPoint ? width.value : NAN, height.unit == YGUnitPoint ? height.value : NAN); -} - -- (void)setSize:(CGSize)size -{ - YGNodeStyleSetWidth(_yogaNode, size.width); - YGNodeStyleSetHeight(_yogaNode, size.height); -} - -// IntrinsicContentSize - -static inline YGSize -RCTShadowViewMeasure(YGNodeConstRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode) -{ - RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node); - - CGSize intrinsicContentSize = shadowView->_intrinsicContentSize; - // Replace `UIViewNoIntrinsicMetric` (which equals `-1`) with zero. - intrinsicContentSize.width = MAX(0, intrinsicContentSize.width); - intrinsicContentSize.height = MAX(0, intrinsicContentSize.height); - - YGSize result; - - switch (widthMode) { - case YGMeasureModeUndefined: - result.width = intrinsicContentSize.width; - break; - case YGMeasureModeExactly: - result.width = width; - break; - case YGMeasureModeAtMost: - result.width = MIN(width, intrinsicContentSize.width); - break; - } - - switch (heightMode) { - case YGMeasureModeUndefined: - result.height = intrinsicContentSize.height; - break; - case YGMeasureModeExactly: - result.height = height; - break; - case YGMeasureModeAtMost: - result.height = MIN(height, intrinsicContentSize.height); - break; - } - - return result; -} - -- (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize -{ - if (CGSizeEqualToSize(_intrinsicContentSize, intrinsicContentSize)) { - return; - } - - _intrinsicContentSize = intrinsicContentSize; - - if (CGSizeEqualToSize(_intrinsicContentSize, CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric))) { - YGNodeSetMeasureFunc(_yogaNode, NULL); - } else { - YGNodeSetMeasureFunc(_yogaNode, RCTShadowViewMeasure); - } - - YGNodeMarkDirty(_yogaNode); -} - -// Local Data - -- (void)setLocalData:(__unused NSObject *)localData -{ - // Do nothing by default. -} - -// Flex - -- (void)setFlexBasis:(YGValue)value -{ - RCT_SET_YGVALUE_AUTO_INTRINSIC(value, YGNodeStyleSetFlexBasis, _yogaNode); -} - -- (YGValue)flexBasis -{ - return YGNodeStyleGetFlexBasis(_yogaNode); -} - -#define RCT_GAP_PROPERTY(setProp, getProp, cssProp, gutter) \ - -(void)set##setProp : (YGValue)value \ - { \ - RCT_SET_YGVALUE(value, YGNodeStyleSetGap, _yogaNode, gutter); \ - } \ - -(YGValue)getProp \ - { \ - return YGNodeStyleGet##cssProp(_yogaNode, gutter); \ - } - -RCT_GAP_PROPERTY(RowGap, rowGap, Gap, YGGutterRow); -RCT_GAP_PROPERTY(ColumnGap, columnGap, Gap, YGGutterColumn); -RCT_GAP_PROPERTY(Gap, gap, Gap, YGGutterAll); - -#define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \ - -(void)set##setProp : (type)value \ - { \ - YGNodeStyleSet##cssProp(_yogaNode, value); \ - } \ - -(type)getProp \ - { \ - return YGNodeStyleGet##cssProp(_yogaNode); \ - } - -RCT_STYLE_PROPERTY(Flex, flex, Flex, float) -RCT_STYLE_PROPERTY(FlexGrow, flexGrow, FlexGrow, float) -RCT_STYLE_PROPERTY(FlexShrink, flexShrink, FlexShrink, float) -RCT_STYLE_PROPERTY(FlexDirection, flexDirection, FlexDirection, YGFlexDirection) -RCT_STYLE_PROPERTY(JustifyContent, justifyContent, JustifyContent, YGJustify) -RCT_STYLE_PROPERTY(AlignSelf, alignSelf, AlignSelf, YGAlign) -RCT_STYLE_PROPERTY(AlignItems, alignItems, AlignItems, YGAlign) -RCT_STYLE_PROPERTY(AlignContent, alignContent, AlignContent, YGAlign) -RCT_STYLE_PROPERTY(Position, position, PositionType, YGPositionType) -RCT_STYLE_PROPERTY(FlexWrap, flexWrap, FlexWrap, YGWrap) -RCT_STYLE_PROPERTY(Overflow, overflow, Overflow, YGOverflow) -RCT_STYLE_PROPERTY(Display, display, Display, YGDisplay) -RCT_STYLE_PROPERTY(Direction, direction, Direction, YGDirection) -RCT_STYLE_PROPERTY(AspectRatio, aspectRatio, AspectRatio, float) - -- (void)didUpdateReactSubviews -{ - // Does nothing by default -} - -- (void)didSetProps:(__unused NSArray *)changedProps -{ - if (_recomputePadding) { - RCTProcessMetaPropsPadding(_paddingMetaProps, _yogaNode); - } - if (_recomputeMargin) { - RCTProcessMetaPropsMargin(_marginMetaProps, _yogaNode); - } - if (_recomputeBorder) { - RCTProcessMetaPropsBorder(_borderMetaProps, _yogaNode); - } - _recomputeMargin = NO; - _recomputePadding = NO; - _recomputeBorder = NO; -} - -@end - -#else // RCT_REMOVE_LEGACY_ARCH @implementation RCTShadowView @synthesize reactTag = _reactTag; @synthesize rootTag = _rootTag; @@ -825,4 +103,3 @@ - (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor } @end -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTSwitch.h b/packages/react-native/React/Views/RCTSwitch.h index d1df01b8a31..3f93aa3f360 100644 --- a/packages/react-native/React/Views/RCTSwitch.h +++ b/packages/react-native/React/Views/RCTSwitch.h @@ -6,17 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSwitch : UISwitch - -@property (nonatomic, assign) BOOL wasOn; -@property (nonatomic, copy) RCTBubblingEventBlock onChange; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTSwitch.m b/packages/react-native/React/Views/RCTSwitch.m index ffee215f9c8..56a16139bef 100644 --- a/packages/react-native/React/Views/RCTSwitch.m +++ b/packages/react-native/React/Views/RCTSwitch.m @@ -6,19 +6,3 @@ */ #import "RCTSwitch.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "UIView+React.h" - -@implementation RCTSwitch - -- (void)setOn:(BOOL)on animated:(BOOL)animated -{ - _wasOn = on; - [super setOn:on animated:animated]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTSwitchManager.h b/packages/react-native/React/Views/RCTSwitchManager.h index bb96504ca21..b6bc9e2e3c2 100644 --- a/packages/react-native/React/Views/RCTSwitchManager.h +++ b/packages/react-native/React/Views/RCTSwitchManager.h @@ -6,12 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSwitchManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RCTSwitchManager.m b/packages/react-native/React/Views/RCTSwitchManager.m index 5398419a4c0..9a381eeeb18 100644 --- a/packages/react-native/React/Views/RCTSwitchManager.m +++ b/packages/react-native/React/Views/RCTSwitchManager.m @@ -6,65 +6,3 @@ */ #import "RCTSwitchManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import "RCTBridge.h" -#import "RCTSwitch.h" -#import "UIView+React.h" - -@implementation RCTSwitchManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - RCTSwitch *switcher = [RCTSwitch new]; - [switcher addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; - return switcher; -} - -- (void)onChange:(RCTSwitch *)sender -{ - if (sender.wasOn != sender.on) { - if (sender.onChange) { - sender.onChange(@{@"value" : @(sender.on)}); - } - sender.wasOn = sender.on; - } -} - -RCT_EXPORT_METHOD(setValue : (nonnull NSNumber *)viewTag toValue : (BOOL)value) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[viewTag]; - - if ([view isKindOfClass:[UISwitch class]]) { - [(UISwitch *)view setOn:value animated:NO]; - } else { - RCTLogError(@"view type must be UISwitch"); - } - }]; -} - -RCT_EXPORT_VIEW_PROPERTY(onTintColor, UIColor); -RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor); -RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor); -RCT_REMAP_VIEW_PROPERTY(value, on, BOOL); -RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock); -RCT_CUSTOM_VIEW_PROPERTY(disabled, BOOL, RCTSwitch) -{ - if (json) { - view.enabled = !([RCTConvert BOOL:json]); - } else { - view.enabled = defaultView.enabled; - } -} -RCT_REMAP_VIEW_PROPERTY(thumbColor, thumbTintColor, UIColor); -RCT_REMAP_VIEW_PROPERTY(trackColorForFalse, tintColor, UIColor); -RCT_REMAP_VIEW_PROPERTY(trackColorForTrue, onTintColor, UIColor); - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.h index ed306d7cadb..3f93aa3f360 100644 --- a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.h +++ b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.h @@ -6,19 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTRefreshControl : UIRefreshControl - -@property (nonatomic, copy) NSString *title; -@property (nonatomic, copy) RCTDirectEventBlock onRefresh; -@property (nonatomic, weak) UIScrollView *scrollView; - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m index 2dc86e46426..e3bc23d2950 100644 --- a/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m +++ b/packages/react-native/React/Views/RefreshControl/RCTRefreshControl.m @@ -7,224 +7,3 @@ #import "RCTRefreshControl.h" #import "RCTRefreshableProtocol.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTUtils.h" - -@interface RCTRefreshControl () -@end - -@implementation RCTRefreshControl { - BOOL _isInitialRender; - BOOL _currentRefreshingState; - UInt64 _currentRefreshingStateClock; - UInt64 _currentRefreshingStateTimestamp; - BOOL _refreshingProgrammatically; - NSString *_title; - UIColor *_titleColor; - CGFloat _progressViewOffset; - BOOL _hasMovedToWindow; -} - -- (instancetype)init -{ - if ((self = [super init])) { - [self addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; - _currentRefreshingStateClock = 1; - _currentRefreshingStateTimestamp = 0; - _isInitialRender = true; - _currentRefreshingState = false; - _hasMovedToWindow = NO; - } - return self; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [self _applyProgressViewOffset]; - - // Fix for bug #7976 - if (self.backgroundColor == nil) { - self.backgroundColor = [UIColor clearColor]; - } - - // If the control is refreshing when mounted we need to call - // beginRefreshing in layoutSubview or it doesn't work. - if (_currentRefreshingState && _isInitialRender) { - [self beginRefreshingProgrammatically]; - } - _isInitialRender = false; -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - - if (self.window) { - _hasMovedToWindow = YES; - } else { - _hasMovedToWindow = NO; - } -} - -- (void)beginRefreshingProgrammatically -{ - if (!_hasMovedToWindow) { - return; - } - - UInt64 beginRefreshingTimestamp = _currentRefreshingStateTimestamp; - _refreshingProgrammatically = YES; - - // Fix for bug #24855 - [self sizeToFit]; - - if (self.scrollView) { - // When using begin refreshing we need to adjust the ScrollView content offset manually. - UIScrollView *scrollView = (UIScrollView *)self.scrollView; - - CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height}; - - // `beginRefreshing` must be called after the animation is done. This is why it is impossible - // to use `setContentOffset` with `animated:YES`. - [UIView animateWithDuration:0.25 - delay:0 - options:UIViewAnimationOptionBeginFromCurrentState - animations:^(void) { - [scrollView setContentOffset:offset]; - } - completion:^(__unused BOOL finished) { - if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) { - [super beginRefreshing]; - [self setCurrentRefreshingState:super.refreshing]; - } - }]; - } else if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) { - [super beginRefreshing]; - [self setCurrentRefreshingState:super.refreshing]; - } -} - -- (void)endRefreshingProgrammatically -{ - if (!_hasMovedToWindow) { - return; - } - // The contentOffset of the scrollview MUST be greater than the contentInset before calling - // endRefreshing otherwise the next pull to refresh will not work properly. - UIScrollView *scrollView = self.scrollView; - if (scrollView && _refreshingProgrammatically && scrollView.contentOffset.y < -scrollView.contentInset.top) { - UInt64 endRefreshingTimestamp = _currentRefreshingStateTimestamp; - CGPoint offset = {scrollView.contentOffset.x, -scrollView.contentInset.top}; - [UIView animateWithDuration:0.25 - delay:0 - options:UIViewAnimationOptionBeginFromCurrentState - animations:^(void) { - [scrollView setContentOffset:offset]; - } - completion:^(__unused BOOL finished) { - if (endRefreshingTimestamp == self->_currentRefreshingStateTimestamp) { - [super endRefreshing]; - [self setCurrentRefreshingState:super.refreshing]; - } - }]; - } else { - [super endRefreshing]; - } -} - -- (void)_applyProgressViewOffset -{ - // Setting the UIRefreshControl's frame breaks integration with ContentInset from the superview - // if it is a UIScrollView. This integration happens when setting the UIScrollView's .refreshControl - // property. For this reason, setting the frame manually should be avoided, if not needed. - if (_progressViewOffset == 0.f) { - return; - } - - // progressViewOffset must be converted from the ScrollView parent's coordinate space to - // the coordinate space of the RefreshControl. This ensures that the control respects any - // offset in the view hierarchy, and that progressViewOffset is not inadvertently applied - // multiple times. - UIView *scrollView = self.superview; - UIView *target = scrollView.superview; - CGPoint rawOffset = CGPointMake(0, _progressViewOffset); - CGPoint converted = [self convertPoint:rawOffset fromView:target]; - self.frame = CGRectOffset(self.frame, 0, converted.y); -} - -- (NSString *)title -{ - return _title; -} - -- (void)setTitle:(NSString *)title -{ - _title = title; - [self _updateTitle]; -} - -- (void)setTitleColor:(UIColor *)color -{ - _titleColor = color; - [self _updateTitle]; -} - -- (void)_updateTitle -{ - if (!_title) { - return; - } - - NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; - if (_titleColor) { - attributes[NSForegroundColorAttributeName] = _titleColor; - } - - self.attributedTitle = [[NSAttributedString alloc] initWithString:_title attributes:attributes]; -} - -- (void)setRefreshing:(BOOL)refreshing -{ - if (_currentRefreshingState != refreshing) { - [self setCurrentRefreshingState:refreshing]; - - if (refreshing) { - if (!_isInitialRender) { - [self beginRefreshingProgrammatically]; - } - } else { - [self endRefreshingProgrammatically]; - } - } -} - -- (void)setCurrentRefreshingState:(BOOL)refreshing -{ - _currentRefreshingState = refreshing; - _currentRefreshingStateTimestamp = _currentRefreshingStateClock++; -} - -- (void)setProgressViewOffset:(CGFloat)offset -{ - _progressViewOffset = offset; - [self _applyProgressViewOffset]; -} - -- (void)refreshControlValueChanged -{ - [self setCurrentRefreshingState:super.refreshing]; - _refreshingProgrammatically = NO; - - if (_onRefresh) { - _onRefresh(nil); - } -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.h b/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.h index 12810fe1ead..b6bc9e2e3c2 100644 --- a/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.h +++ b/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.h @@ -6,12 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTRefreshControlManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m index 1e9ff527f4e..141808b23a5 100644 --- a/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m +++ b/packages/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m @@ -6,42 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTRefreshControl.h" -#import "RCTRefreshControlManager.h" -#import "RCTRefreshableProtocol.h" - -@implementation RCTRefreshControlManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - return [RCTRefreshControl new]; -} - -RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL) -RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) -RCT_EXPORT_VIEW_PROPERTY(title, NSString) -RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor) -RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat) - -RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[viewTag]; - - if ([view conformsToProtocol:@protocol(RCTRefreshableProtocol)]) { - [(id)view setRefreshing:refreshing]; - } else { - RCTLogError(@"view must conform to protocol RCTRefreshableProtocol"); - } - }]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.h b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.h index f1dbebda57e..103dec8dfad 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.h +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.h @@ -6,16 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSafeAreaShadowView : RCTShadowView - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.m b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.m index 3a4aa3f92a0..dd0201c510a 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.m +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaShadowView.m @@ -6,52 +6,3 @@ */ #import "RCTSafeAreaShadowView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -#import "RCTSafeAreaViewLocalData.h" - -@implementation RCTSafeAreaShadowView - -- (void)setLocalData:(RCTSafeAreaViewLocalData *)localData -{ - RCTAssert( - [localData isKindOfClass:[RCTSafeAreaViewLocalData class]], - @"Local data object for `RCTSafeAreaShadowView` must be `RCTSafeAreaViewLocalData` instance."); - - UIEdgeInsets insets = localData.insets; - - super.paddingLeft = (YGValue){insets.left, YGUnitPoint}; - super.paddingRight = (YGValue){insets.right, YGUnitPoint}; - super.paddingTop = (YGValue){insets.top, YGUnitPoint}; - super.paddingBottom = (YGValue){insets.bottom, YGUnitPoint}; - - [self didSetProps:@[ @"paddingLeft", @"paddingRight", @"paddingTop", @"paddingBottom" ]]; -} - -/** - * Removing support for setting padding from any outside code - * to prevent interfering this with local data. - */ -- (void)setPadding:(__unused YGValue)value -{ -} -- (void)setPaddingLeft:(__unused YGValue)value -{ -} -- (void)setPaddingRight:(__unused YGValue)value -{ -} -- (void)setPaddingTop:(__unused YGValue)value -{ -} -- (void)setPaddingBottom:(__unused YGValue)value -{ -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.h b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.h index 08a9794b0f3..3f93aa3f360 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.h +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.h @@ -6,22 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class RCTBridge; - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSafeAreaView : RCTView - -- (instancetype)initWithBridge:(RCTBridge *)bridge; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.m b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.m index 3069734246a..7f6df12778e 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.m +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaView.m @@ -6,74 +6,3 @@ */ #import "RCTSafeAreaView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -#import "RCTSafeAreaViewLocalData.h" - -@implementation RCTSafeAreaView { - __weak RCTBridge *_bridge; - UIEdgeInsets _currentSafeAreaInsets; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - if (self = [super initWithFrame:CGRectZero]) { - _bridge = bridge; - } - - return self; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)decoder) -RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) - -- (NSString *)description -{ - NSString *superDescription = [super description]; - - // Cutting the last `>` character. - if (superDescription.length > 0 && [superDescription characterAtIndex:superDescription.length - 1] == '>') { - superDescription = [superDescription substringToIndex:superDescription.length - 1]; - } - - return [NSString stringWithFormat:@"%@; safeAreaInsets = %@; appliedSafeAreaInsets = %@>", - superDescription, - NSStringFromUIEdgeInsets(self.safeAreaInsets), - NSStringFromUIEdgeInsets(_currentSafeAreaInsets)]; -} - -static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) -{ - return ABS(insets1.left - insets2.left) <= threshold && ABS(insets1.right - insets2.right) <= threshold && - ABS(insets1.top - insets2.top) <= threshold && ABS(insets1.bottom - insets2.bottom) <= threshold; -} - -- (void)safeAreaInsetsDidChange -{ - [self setSafeAreaInsets:self.safeAreaInsets]; -} - -- (void)setSafeAreaInsets:(UIEdgeInsets)safeAreaInsets -{ - // Relayout with different padding may result in a close but slightly different result, amplified by Yoga rounding to - // physical pixel grid. To avoid infinite relayout, allow one physical pixel of difference, along with small amount of - // extra tolerance for FP error. - CGFloat tolerance = 1.0 / RCTScreenScale() + 0.01; - - if (UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, tolerance)) { - return; - } - - _currentSafeAreaInsets = safeAreaInsets; - - RCTSafeAreaViewLocalData *localData = [[RCTSafeAreaViewLocalData alloc] initWithInsets:safeAreaInsets]; - [_bridge.uiManager setLocalData:localData forView:self]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.h b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.h index 6df07b7a7fa..3f93aa3f360 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.h +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.h @@ -6,20 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSafeAreaViewLocalData : NSObject - -- (instancetype)initWithInsets:(UIEdgeInsets)insets; - -@property (atomic, readonly) UIEdgeInsets insets; - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.m b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.m index 412ea131f38..a016bc91570 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.m +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.m @@ -6,20 +6,3 @@ */ #import "RCTSafeAreaViewLocalData.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -@implementation RCTSafeAreaViewLocalData - -- (instancetype)initWithInsets:(UIEdgeInsets)insets -{ - if (self = [super init]) { - _insets = insets; - } - - return self; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.h b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.h index ad889b1b7cc..b6bc9e2e3c2 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.h +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.h @@ -6,16 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTSafeAreaViewManager : RCTViewManager - -@end - -NS_ASSUME_NONNULL_END - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.m b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.m index f8d2d8696dd..abee46e61c2 100644 --- a/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.m +++ b/packages/react-native/React/Views/SafeAreaView/RCTSafeAreaViewManager.m @@ -6,27 +6,3 @@ */ #import "RCTSafeAreaViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTSafeAreaShadowView.h" -#import "RCTSafeAreaView.h" -#import "RCTUIManager.h" - -@implementation RCTSafeAreaViewManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - return [[RCTSafeAreaView alloc] initWithBridge:self.bridge]; -} - -- (RCTSafeAreaShadowView *)shadowView -{ - return [RCTSafeAreaShadowView new]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.h b/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.h index ced82fdd933..3f93aa3f360 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.h @@ -6,14 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTScrollContentShadowView : RCTShadowView - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.m b/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.m index e14d4141f81..9c29718fd60 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.m @@ -6,30 +6,3 @@ */ #import "RCTScrollContentShadowView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import "RCTUtils.h" - -@implementation RCTScrollContentShadowView - -- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics layoutContext:(RCTLayoutContext)layoutContext -{ - if (layoutMetrics.layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { - // Motivation: - // Yoga place `contentView` on the right side of `scrollView` when RTL layout is enforced. - // That breaks everything; it is completely pointless to (re)position `contentView` - // because it is `contentView`'s job. So, we work around it here. - - layoutContext.absolutePosition.x += layoutMetrics.frame.size.width; - layoutMetrics.frame.origin.x = 0; - } - - [super layoutWithMetrics:layoutMetrics layoutContext:layoutContext]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollContentView.h b/packages/react-native/React/Views/ScrollView/RCTScrollContentView.h index b9033f1d0ff..3f93aa3f360 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollContentView.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollContentView.h @@ -6,14 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTScrollContentView : RCTView - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollContentView.m b/packages/react-native/React/Views/ScrollView/RCTScrollContentView.m index 82657ffd845..d7f26f590d7 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollContentView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollContentView.m @@ -6,31 +6,3 @@ */ #import "RCTScrollContentView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import - -#import "RCTScrollView.h" - -@implementation RCTScrollContentView - -- (void)reactSetFrame:(CGRect)frame -{ - [super reactSetFrame:frame]; - - RCTScrollView *scrollView = (RCTScrollView *)self.superview.superview; - - if (!scrollView) { - return; - } - - RCTAssert([scrollView isKindOfClass:[RCTScrollView class]], @"Unexpected view hierarchy of RCTScrollView component."); - - [scrollView updateContentSizeIfNeeded]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.h b/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.h index e46e0b47baf..b6bc9e2e3c2 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.h @@ -6,12 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTScrollContentViewManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.m b/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.m index 4d79c3404c2..1633010f08a 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollContentViewManager.m @@ -6,26 +6,3 @@ */ #import "RCTScrollContentViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTScrollContentShadowView.h" -#import "RCTScrollContentView.h" - -@implementation RCTScrollContentViewManager - -RCT_EXPORT_MODULE() - -- (RCTScrollContentView *)view -{ - return [RCTScrollContentView new]; -} - -- (RCTShadowView *)shadowView -{ - return [RCTScrollContentShadowView new]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.h b/packages/react-native/React/Views/ScrollView/RCTScrollView.h index 64bdade849e..514658d44de 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.h @@ -6,84 +6,3 @@ */ #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import -#import -#import -#import -#import - -@protocol UIScrollViewDelegate; - -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wdeprecated" -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTScrollView : RCTView -#pragma clang diagnostic pop - -- (instancetype)initWithEventDispatcher:(id)eventDispatcher NS_DESIGNATED_INITIALIZER; - -/** - * The `RCTScrollView` may have at most one single subview. This will ensure - * that the scroll view's `contentSize` will be efficiently set to the size of - * the single subview's frame. That frame size will be determined somewhat - * efficiently since it will have already been computed by the off-main-thread - * layout system. - */ -@property (nonatomic, readonly) UIView *contentView; - -/** - * The underlying scrollView (TODO: can we remove this?) - */ -@property (nonatomic, readonly) UIScrollView *scrollView; - -@property (nonatomic, assign) UIEdgeInsets contentInset; -@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; -@property (nonatomic, assign) BOOL automaticallyAdjustKeyboardInsets; -@property (nonatomic, assign) BOOL DEPRECATED_sendUpdatedChildFrames; -@property (nonatomic, assign) NSTimeInterval scrollEventThrottle; -@property (nonatomic, assign) BOOL centerContent; -@property (nonatomic, copy) NSDictionary *maintainVisibleContentPosition; -@property (nonatomic, assign) BOOL scrollToOverflowEnabled; -@property (nonatomic, assign) int snapToInterval; -@property (nonatomic, assign) BOOL disableIntervalMomentum; -@property (nonatomic, copy) NSArray *snapToOffsets; -@property (nonatomic, assign) BOOL snapToStart; -@property (nonatomic, assign) BOOL snapToEnd; -@property (nonatomic, copy) NSString *snapToAlignment; -@property (nonatomic, assign) BOOL inverted; -/** Focus area of newly-activated text input relative to the window to compare against UIKeyboardFrameBegin/End */ -@property (nonatomic, assign) CGRect firstResponderFocus; -/** newly-activated text input outside of the scroll view */ -@property (nonatomic, weak) UIView *firstResponderViewOutsideScrollView; - -// NOTE: currently these event props are only declared so we can export the -// event names to JS - we don't call the blocks directly because scroll events -// need to be coalesced before sending, for performance reasons. -@property (nonatomic, copy) RCTDirectEventBlock onScrollBeginDrag; -@property (nonatomic, copy) RCTDirectEventBlock onScroll; -@property (nonatomic, copy) RCTDirectEventBlock onScrollToTop; -@property (nonatomic, copy) RCTDirectEventBlock onScrollEndDrag; -@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollBegin; -@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollEnd; - -@end - -@interface UIView (RCTScrollView) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated" -- (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView; -#pragma clang diagnostic pop -@end - -@interface RCTScrollView (Internal) - -- (void)updateContentSizeIfNeeded; - -@end - -RCT_EXTERN void RCTSendFakeScrollEvent(id eventDispatcher, NSNumber *reactTag); - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 72c7c39e3c1..0a907565a6a 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -6,1163 +6,3 @@ */ #import "RCTScrollView.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import - -#import "RCTConvert.h" -#import "RCTLog.h" -#import "RCTRefreshControl.h" -#import "RCTScrollEvent.h" -#import "RCTUIManager.h" -#import "RCTUIManagerObserverCoordinator.h" -#import "RCTUIManagerUtils.h" -#import "RCTUtils.h" -#import "RCTViewUtils.h" -#import "UIView+Private.h" -#import "UIView+React.h" - -/** - * Include a custom scroll view subclass because we want to limit certain - * default UIKit behaviors such as textFields automatically scrolling - * scroll views that contain them. - */ -@interface RCTCustomScrollView : UIScrollView - -@property (nonatomic, assign) BOOL centerContent; -@property (nonatomic, strong) UIView *customRefreshControl; -@property (nonatomic, assign) BOOL pinchGestureEnabled; - -@end - -@implementation RCTCustomScrollView - -- (instancetype)initWithFrame:(CGRect)frame -{ - if ((self = [super initWithFrame:frame])) { - [self.panGestureRecognizer addTarget:self action:@selector(handleCustomPan:)]; - - if ([self respondsToSelector:@selector(setSemanticContentAttribute:)]) { - // We intentionally force `UIScrollView`s `semanticContentAttribute` to `LTR` here - // because this attribute affects a position of vertical scrollbar; we don't want this - // scrollbar flip because we also flip it with whole `UIScrollView` flip. - self.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight; - } - - _pinchGestureEnabled = YES; - } - return self; -} - -- (UIView *)contentView -{ - return ((RCTScrollView *)self.superview).contentView; -} - -/** - * @return Whether or not the scroll view interaction should be blocked because - * JS was found to be the responder. - */ -- (BOOL)_shouldDisableScrollInteraction -{ - // Since this may be called on every pan, we need to make sure to only climb - // the hierarchy on rare occasions. - UIView *JSResponder = [RCTUIManager JSResponder]; - if (JSResponder && JSResponder != self.superview) { - BOOL superviewHasResponder = [self isDescendantOfView:JSResponder]; - return superviewHasResponder; - } - return NO; -} - -- (void)handleCustomPan:(__unused UIPanGestureRecognizer *)sender -{ - if ([self _shouldDisableScrollInteraction] && ![[RCTUIManager JSResponder] isKindOfClass:[RCTScrollView class]]) { - self.panGestureRecognizer.enabled = NO; - self.panGestureRecognizer.enabled = YES; - // TODO: If mid bounce, animate the scroll view to a non-bounced position - // while disabling (but only if `stopScrollInteractionIfJSHasResponder` was - // called *during* a `pan`). Currently, it will just snap into place which - // is not so bad either. - // Another approach: - // self.scrollEnabled = NO; - // self.scrollEnabled = YES; - } -} - -- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated -{ - // Limiting scroll area to an area where we actually have content. - CGSize contentSize = self.contentSize; - UIEdgeInsets contentInset = self.contentInset; - CGSize fullSize = CGSizeMake( - contentSize.width + contentInset.left + contentInset.right, - contentSize.height + contentInset.top + contentInset.bottom); - - rect = CGRectIntersection((CGRect){CGPointZero, fullSize}, rect); - if (CGRectIsNull(rect)) { - return; - } - - [super scrollRectToVisible:rect animated:animated]; -} - -/** - * Returning `YES` cancels touches for the "inner" `view` and causes a scroll. - * Returning `NO` causes touches to be directed to that inner view and prevents - * the scroll view from scrolling. - * - * `YES` -> Allows scrolling. - * `NO` -> Doesn't allow scrolling. - * - * By default this returns NO for all views that are UIControls and YES for - * everything else. What that does is allows scroll views to scroll even when a - * touch started inside of a `UIControl` (`UIButton` etc). For React scroll - * views, we want the default to be the same behavior as `UIControl`s so we - * return `YES` by default. But there's one case where we want to block the - * scrolling no matter what: When JS believes it has its own responder lock on - * a view that is *above* the scroll view in the hierarchy. So we abuse this - * `touchesShouldCancelInContentView` API in order to stop the scroll view from - * scrolling in this case. - * - * We are not aware of *any* other solution to the problem because alternative - * approaches require that we disable the scrollview *before* touches begin or - * move. This approach (`touchesShouldCancelInContentView`) works even if the - * JS responder is set after touches start/move because - * `touchesShouldCancelInContentView` is called as soon as the scroll view has - * been touched and dragged *just* far enough to decide to begin the "drag" - * movement of the scroll interaction. Returning `NO`, will cause the drag - * operation to fail. - * - * `touchesShouldCancelInContentView` will stop the *initialization* of a - * scroll pan gesture and most of the time this is sufficient. On rare - * occasion, the scroll gesture would have already initialized right before JS - * notifies native of the JS responder being set. In order to recover from that - * timing issue we have a fallback that kills any ongoing pan gesture that - * occurs when native is notified of a JS responder. - * - * Note: Explicitly returning `YES`, instead of relying on the default fixes - * (at least) one bug where if you have a UIControl inside a UIScrollView and - * tap on the UIControl and then start dragging (to scroll), it won't scroll. - * Chat with @andras for more details. - * - * In order to have this called, you must have delaysContentTouches set to NO - * (which is the not the `UIKit` default). - */ -- (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view -{ - BOOL shouldDisableScrollInteraction = [self _shouldDisableScrollInteraction]; - - if (shouldDisableScrollInteraction == NO) { - [super touchesShouldCancelInContentView:view]; - } - - return !shouldDisableScrollInteraction; -} - -- (void)setContentOffset:(CGPoint)contentOffset -{ - super.contentOffset = CGPointMake( - RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"), - RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y")); -} - -- (void)setFrame:(CGRect)frame -{ - // Preserving and revalidating `contentOffset`. - CGPoint originalOffset = self.contentOffset; - - [super setFrame:frame]; - - UIEdgeInsets contentInset = self.contentInset; - CGSize contentSize = self.contentSize; - - // If contentSize has not been measured yet we can't check bounds. - if (CGSizeEqualToSize(contentSize, CGSizeZero)) { - self.contentOffset = originalOffset; - } else { - if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, self.adjustedContentInset)) { - contentInset = self.adjustedContentInset; - } - CGSize boundsSize = self.bounds.size; - CGFloat xMaxOffset = contentSize.width - boundsSize.width + contentInset.right; - CGFloat yMaxOffset = contentSize.height - boundsSize.height + contentInset.bottom; - // Make sure offset doesn't exceed bounds. This can happen on screen rotation. - if ((originalOffset.x >= -contentInset.left) && (originalOffset.x <= xMaxOffset) && - (originalOffset.y >= -contentInset.top) && (originalOffset.y <= yMaxOffset)) { - return; - } - self.contentOffset = CGPointMake( - MAX(-contentInset.left, MIN(xMaxOffset, originalOffset.x)), - MAX(-contentInset.top, MIN(yMaxOffset, originalOffset.y))); - } -} - -- (void)setCustomRefreshControl:(UIView *)refreshControl -{ - if (_customRefreshControl) { - [_customRefreshControl removeFromSuperview]; - } - _customRefreshControl = refreshControl; - // We have to set this because we can't always guarantee the - // `RCTCustomRefreshControlProtocol`'s superview will always be of class - // `UIScrollView` like we were previously - if ([_customRefreshControl respondsToSelector:@selector(setScrollView:)]) { - _customRefreshControl.scrollView = self; - } - if ([refreshControl isKindOfClass:UIRefreshControl.class]) { - self.refreshControl = (UIRefreshControl *)refreshControl; - } else { - [self addSubview:_customRefreshControl]; - } -} - -- (void)setPinchGestureEnabled:(BOOL)pinchGestureEnabled -{ - self.pinchGestureRecognizer.enabled = pinchGestureEnabled; - _pinchGestureEnabled = pinchGestureEnabled; -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - // ScrollView enables pinch gesture late in its lifecycle. So simply setting it - // in the setter gets overridden when the view loads. - self.pinchGestureRecognizer.enabled = _pinchGestureEnabled; -} - -- (BOOL)shouldGroupAccessibilityChildren -{ - return YES; -} - -@end - -@interface RCTScrollView () - -@end - -@implementation RCTScrollView { - id _eventDispatcher; - CGRect _prevFirstVisibleFrame; - __weak UIView *_firstVisibleView; - RCTCustomScrollView *_scrollView; - UIView *_contentView; - NSTimeInterval _lastScrollDispatchTime; - NSMutableArray *_cachedChildFrames; - BOOL _allowNextScrollNoMatterWhat; - CGRect _lastClippedToRect; - uint16_t _coalescingKey; - NSString *_lastEmittedEventName; - NSHashTable *_scrollListeners; -} - -- (void)_registerKeyboardListener -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_keyboardWillChangeFrame:) - name:UIKeyboardWillChangeFrameNotification - object:nil]; -} - -- (void)_unregisterKeyboardListener -{ - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil]; -} - -static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCurve curve) -{ - // UIViewAnimationCurve #7 is used for keyboard and therefore private - so we can't use switch/case here. - // source: https://stackoverflow.com/a/7327374/5281431 - RCTAssert( - UIViewAnimationCurveLinear << 16 == UIViewAnimationOptionCurveLinear, - @"Unexpected implementation of UIViewAnimationCurve"); - return curve << 16; -} - -- (void)_keyboardWillChangeFrame:(NSNotification *)notification -{ - if (![self automaticallyAdjustKeyboardInsets]) { - return; - } - if ([self isHorizontal:_scrollView]) { - return; - } - - double duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - - UIViewAnimationCurve curve = - (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue]; - CGRect beginFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; - CGRect endFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - CGPoint absoluteViewOrigin = [self convertPoint:self.bounds.origin toView:nil]; - CGFloat scrollViewLowerY = self.inverted ? absoluteViewOrigin.y : absoluteViewOrigin.y + self.bounds.size.height; - - UIEdgeInsets newEdgeInsets = _scrollView.contentInset; - CGFloat inset = MAX(scrollViewLowerY - endFrame.origin.y, 0); - if (self.inverted) { - newEdgeInsets.top = MAX(inset, _contentInset.top); - } else { - newEdgeInsets.bottom = MAX(inset, _contentInset.bottom); - } - - CGPoint newContentOffset = _scrollView.contentOffset; - self.firstResponderFocus = CGRectNull; - - CGFloat contentDiff = 0; - if ([[UIApplication sharedApplication] sendAction:@selector(reactUpdateResponderOffsetForScrollView:) - to:nil - from:self - forEvent:nil]) { - // Inner text field focused - CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus); - BOOL didFocusExternalTextField = focusEnd == INFINITY; - if (!didFocusExternalTextField && focusEnd > endFrame.origin.y) { - // Text field active region is below visible area with keyboard - update diff to bring into view - contentDiff = endFrame.origin.y - focusEnd; - } else { - UIView *inputAccessoryView = _firstResponderViewOutsideScrollView.inputAccessoryView; - if (inputAccessoryView) { - // Text input view is within the inputAccessoryView. - contentDiff = endFrame.origin.y - beginFrame.origin.y; - } - } - } else if (endFrame.origin.y <= beginFrame.origin.y) { - // Keyboard opened for other reason - contentDiff = endFrame.origin.y - beginFrame.origin.y; - } - if (self.inverted) { - newContentOffset.y += contentDiff; - } else { - newContentOffset.y -= contentDiff; - } - - if (@available(iOS 14.0, *)) { - // On iOS when Prefer Cross-Fade Transitions is enabled, the keyboard position - // & height is reported differently (0 instead of Y position value matching height of frame) - // Fixes similar issue we saw with https://github.com/facebook/react-native/pull/34503 - if (UIAccessibilityPrefersCrossFadeTransitions() && endFrame.size.height == 0) { - newContentOffset.y = 0; - newEdgeInsets.bottom = 0; - } - } - - [UIView animateWithDuration:duration - delay:0.0 - options:animationOptionsWithCurve(curve) - animations:^{ - self->_scrollView.contentInset = newEdgeInsets; - self->_scrollView.verticalScrollIndicatorInsets = newEdgeInsets; - [self scrollToOffset:newContentOffset animated:NO]; - } - completion:nil]; -} - -- (instancetype)initWithEventDispatcher:(id)eventDispatcher -{ - RCTAssertParam(eventDispatcher); - - if ((self = [super initWithFrame:CGRectZero])) { - [self _registerKeyboardListener]; - _eventDispatcher = eventDispatcher; - - _scrollView = [[RCTCustomScrollView alloc] initWithFrame:CGRectZero]; - _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _scrollView.delegate = self; - _scrollView.delaysContentTouches = NO; - - // We set the default behavior to "never" so that iOS - // doesn't do weird things to UIScrollView insets automatically - // and keeps it as an opt-in behavior. - _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - - _automaticallyAdjustContentInsets = YES; - _contentInset = UIEdgeInsetsZero; - _lastClippedToRect = CGRectNull; - - _scrollEventThrottle = 0.0; - _lastScrollDispatchTime = 0; - _cachedChildFrames = [NSMutableArray new]; - - _scrollListeners = [NSHashTable weakObjectsHashTable]; - - [self addSubview:_scrollView]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) -RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) - -static inline void RCTApplyTransformationAccordingLayoutDirection( - UIView *view, - UIUserInterfaceLayoutDirection layoutDirection) -{ - view.transform = layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight ? CGAffineTransformIdentity - : CGAffineTransformMakeScale(-1, 1); -} - -- (void)setReactLayoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection -{ - [super setReactLayoutDirection:layoutDirection]; - - RCTApplyTransformationAccordingLayoutDirection(_scrollView, layoutDirection); - RCTApplyTransformationAccordingLayoutDirection(_contentView, layoutDirection); -} - -- (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews -{ - // Does nothing -} - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - [self centerContentIfNeeded]; -} - -- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex -{ - [super insertReactSubview:view atIndex:atIndex]; - if ([view conformsToProtocol:@protocol(RCTCustomRefreshControlProtocol)]) { - [_scrollView setCustomRefreshControl:(UIView *)view]; - if (![view isKindOfClass:[UIRefreshControl class]] && [view conformsToProtocol:@protocol(UIScrollViewDelegate)]) { - [self addScrollListener:(UIView *)view]; - } - } else { - RCTAssert( - _contentView == nil, - @"RCTScrollView may only contain a single subview, the already set subview looks like: %@", - [_contentView react_recursiveDescription]); - _contentView = view; - RCTApplyTransformationAccordingLayoutDirection(_contentView, self.reactLayoutDirection); - [_scrollView addSubview:view]; - } - - [self centerContentIfNeeded]; -} - -- (void)removeReactSubview:(UIView *)subview -{ - [super removeReactSubview:subview]; - if ([subview conformsToProtocol:@protocol(RCTCustomRefreshControlProtocol)]) { - [_scrollView setCustomRefreshControl:nil]; - if (![subview isKindOfClass:[UIRefreshControl class]] && - [subview conformsToProtocol:@protocol(UIScrollViewDelegate)]) { - [self removeScrollListener:(UIView *)subview]; - } - } else { - RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview"); - _contentView = nil; - } -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as subviews are managed by `insertReactSubview:atIndex:` -} - -- (void)didSetProps:(NSArray *)changedProps -{ - if ([changedProps containsObject:@"contentSize"]) { - [self updateContentSizeIfNeeded]; - } -} - -- (BOOL)centerContent -{ - return _scrollView.centerContent; -} - -- (void)setCenterContent:(BOOL)centerContent -{ - _scrollView.centerContent = centerContent; -} - -- (void)setClipsToBounds:(BOOL)clipsToBounds -{ - super.clipsToBounds = clipsToBounds; - _scrollView.clipsToBounds = clipsToBounds; -} - -- (void)dealloc -{ - _scrollView.delegate = nil; - [_eventDispatcher.bridge.uiManager.observerCoordinator removeObserver:self]; - [self _unregisterKeyboardListener]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - RCTAssert(self.subviews.count == 1, @"we should only have exactly one subview"); - RCTAssert([self.subviews lastObject] == _scrollView, @"our only subview should be a scrollview"); - -#if !TARGET_OS_TV - // Adjust the refresh control frame if the scrollview layout changes. - UIView *refreshControl = _scrollView.customRefreshControl; - if (refreshControl && refreshControl.isRefreshing && ![refreshControl isKindOfClass:UIRefreshControl.class]) { - refreshControl.frame = - (CGRect){_scrollView.contentOffset, {_scrollView.frame.size.width, refreshControl.frame.size.height}}; - } -#endif - - [self updateClippedSubviews]; -} - -- (void)updateClippedSubviews -{ - // Find a suitable view to use for clipping - UIView *clipView = [self react_findClipView]; - if (!clipView) { - return; - } - - static const CGFloat leeway = 1.0; - - const CGSize contentSize = _scrollView.contentSize; - const CGRect bounds = _scrollView.bounds; - const BOOL scrollsHorizontally = contentSize.width > bounds.size.width; - const BOOL scrollsVertically = contentSize.height > bounds.size.height; - - const BOOL shouldClipAgain = CGRectIsNull(_lastClippedToRect) || !CGRectEqualToRect(_lastClippedToRect, bounds) || - (scrollsHorizontally && - (bounds.size.width < leeway || fabs(_lastClippedToRect.origin.x - bounds.origin.x) >= leeway)) || - (scrollsVertically && - (bounds.size.height < leeway || fabs(_lastClippedToRect.origin.y - bounds.origin.y) >= leeway)); - - if (shouldClipAgain) { - const CGRect clipRect = CGRectInset(clipView.bounds, -leeway, -leeway); - [self react_updateClippedSubviewsWithClipRect:clipRect relativeToView:clipView]; - _lastClippedToRect = bounds; - } -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - if (UIEdgeInsetsEqualToEdgeInsets(contentInset, _contentInset)) { - return; - } - - CGPoint contentOffset = _scrollView.contentOffset; - - _contentInset = contentInset; - [RCTView autoAdjustInsetsForView:self withScrollView:_scrollView updateOffset:NO]; - - _scrollView.contentOffset = contentOffset; -} - -- (BOOL)isHorizontal:(UIScrollView *)scrollView -{ - return scrollView.contentSize.width > self.frame.size.width; -} - -- (void)scrollToOffset:(CGPoint)offset -{ - [self scrollToOffset:offset animated:YES]; -} - -- (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated -{ - if ([self reactLayoutDirection] == UIUserInterfaceLayoutDirectionRightToLeft) { - offset.x = _scrollView.contentSize.width - _scrollView.frame.size.width - offset.x; - } - - if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) { - CGRect maxRect = CGRectMake( - fmin(-_scrollView.contentInset.left, 0), - fmin(-_scrollView.contentInset.top, 0), - fmax( - _scrollView.contentSize.width - _scrollView.bounds.size.width + _scrollView.contentInset.right + - fmax(_scrollView.contentInset.left, 0), - 0.01), - fmax( - _scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.contentInset.bottom + - fmax(_scrollView.contentInset.top, 0), - 0.01)); // Make width and height greater than 0 - // Ensure at least one scroll event will fire - _allowNextScrollNoMatterWhat = YES; - if (!CGRectContainsPoint(maxRect, offset) && !self.scrollToOverflowEnabled) { - CGFloat x = fmax(offset.x, CGRectGetMinX(maxRect)); - x = fmin(x, CGRectGetMaxX(maxRect)); - CGFloat y = fmax(offset.y, CGRectGetMinY(maxRect)); - y = fmin(y, CGRectGetMaxY(maxRect)); - offset = CGPointMake(x, y); - } - [_scrollView setContentOffset:offset animated:animated]; - } -} - -/** - * If this is a vertical scroll view, scrolls to the bottom. - * If this is a horizontal scroll view, scrolls to the right. - */ -- (void)scrollToEnd:(BOOL)animated -{ - BOOL isHorizontal = [self isHorizontal:_scrollView]; - CGPoint offset; - if (isHorizontal) { - CGFloat offsetX = _scrollView.contentSize.width - _scrollView.bounds.size.width + _scrollView.contentInset.right; - offset = CGPointMake(fmax(offsetX, 0), 0); - } else { - CGFloat offsetY = _scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.contentInset.bottom; - offset = CGPointMake(0, fmax(offsetY, 0)); - } - if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) { - // Ensure at least one scroll event will fire - _allowNextScrollNoMatterWhat = YES; - [_scrollView setContentOffset:offset animated:animated]; - } -} - -- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated -{ - [_scrollView zoomToRect:rect animated:animated]; -} - -- (void)refreshContentInset -{ - [RCTView autoAdjustInsetsForView:self withScrollView:_scrollView updateOffset:YES]; -} - -#pragma mark - ScrollView delegate - -#define RCT_SEND_SCROLL_EVENT(_eventName, _userData) \ - { \ - NSString *eventName = NSStringFromSelector(@selector(_eventName)); \ - [self sendScrollEventWithName:eventName scrollView:_scrollView userData:_userData]; \ - } - -#define RCT_FORWARD_SCROLL_EVENT(call) \ - for (NSObject * scrollViewListener in _scrollListeners) { \ - if ([scrollViewListener respondsToSelector:_cmd]) { \ - [scrollViewListener call]; \ - } \ - } - -#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \ - -(void)delegateMethod : (UIScrollView *)scrollView \ - { \ - RCT_SEND_SCROLL_EVENT(eventName, nil); \ - RCT_FORWARD_SCROLL_EVENT(delegateMethod : scrollView); \ - } - -RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin) -RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop) - -- (void)scrollViewDidZoom:(UIScrollView *)scrollView -{ - [self centerContentIfNeeded]; - - RCT_SEND_SCROLL_EVENT(onScroll, nil); - RCT_FORWARD_SCROLL_EVENT(scrollViewDidZoom : scrollView); -} - -/* - * Automatically centers the content such that if the content is smaller than the - * ScrollView, we force it to be centered, but when you zoom or the content otherwise - * becomes larger than the ScrollView, there is no padding around the content but it - * can still fill the whole view. - * This implementation is based on https://petersteinberger.com/blog/2013/how-to-center-uiscrollview/. - */ -- (void)centerContentIfNeeded -{ - if (!_scrollView.centerContent) { - return; - } - - CGSize contentSize = self.contentSize; - CGSize boundsSize = self.bounds.size; - if (CGSizeEqualToSize(contentSize, CGSizeZero) || CGSizeEqualToSize(boundsSize, CGSizeZero)) { - return; - } - - CGFloat top = 0; - CGFloat left = 0; - if (contentSize.width < boundsSize.width) { - left = (boundsSize.width - contentSize.width) * 0.5f; - } - if (contentSize.height < boundsSize.height) { - top = (boundsSize.height - contentSize.height) * 0.5f; - } - _scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left); -} - -- (void)addScrollListener:(NSObject *)scrollListener -{ - [_scrollListeners addObject:scrollListener]; -} - -- (void)removeScrollListener:(NSObject *)scrollListener -{ - [_scrollListeners removeObject:scrollListener]; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - NSTimeInterval now = CACurrentMediaTime(); - [self updateClippedSubviews]; - /** - * TODO: this logic looks wrong, and it may be because it is. Currently, if _scrollEventThrottle - * is set to zero (the default), the "didScroll" event is only sent once per scroll, instead of repeatedly - * while scrolling as expected. However, if you "fix" that bug, ScrollView will generate repeated - * warnings, and behave strangely (ListView works fine however), so don't fix it unless you fix that too! - * - * We limit the delta to 17ms so that small throttles intended to enable 60fps updates will not - * inadvertently filter out any scroll events. - */ - if (_allowNextScrollNoMatterWhat || (_scrollEventThrottle < MAX(0.017, now - _lastScrollDispatchTime))) { - RCT_SEND_SCROLL_EVENT(onScroll, nil); - // Update dispatch time - _lastScrollDispatchTime = now; - _allowNextScrollNoMatterWhat = NO; - } - RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll : scrollView); -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - _allowNextScrollNoMatterWhat = YES; // Ensure next scroll event is recorded, regardless of throttle - RCT_SEND_SCROLL_EVENT(onScrollBeginDrag, nil); - RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginDragging : scrollView); -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView - withVelocity:(CGPoint)velocity - targetContentOffset:(inout CGPoint *)targetContentOffset -{ - if (self.snapToOffsets) { - // An alternative to enablePaging and snapToInterval which allows setting custom - // stopping points that don't have to be the same distance apart. Often seen in - // apps which feature horizonally scrolling items. snapToInterval does not enforce - // scrolling one interval at a time but guarantees that the scroll will stop at - // a snap offset point. - - // Find which axis to snap - BOOL isHorizontal = [self isHorizontal:scrollView]; - CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y; - CGFloat offsetAlongAxis = isHorizontal ? _scrollView.contentOffset.x : _scrollView.contentOffset.y; - - // Calculate maximum content offset - CGSize viewportSize = [self _calculateViewportSize]; - CGFloat maximumOffset = isHorizontal ? MAX(0, _scrollView.contentSize.width - viewportSize.width) - : MAX(0, _scrollView.contentSize.height - viewportSize.height); - - // Calculate the snap offsets adjacent to the initial offset target - CGFloat targetOffset = isHorizontal ? targetContentOffset->x : targetContentOffset->y; - CGFloat smallerOffset = 0.0; - CGFloat largerOffset = maximumOffset; - - for (unsigned long i = 0; i < self.snapToOffsets.count; i++) { - CGFloat offset = [[self.snapToOffsets objectAtIndex:i] floatValue]; - - if (offset <= targetOffset) { - if (targetOffset - offset < targetOffset - smallerOffset) { - smallerOffset = offset; - } - } - - if (offset >= targetOffset) { - if (offset - targetOffset < largerOffset - targetOffset) { - largerOffset = offset; - } - } - } - - // Calculate the nearest offset - CGFloat nearestOffset = targetOffset - smallerOffset < largerOffset - targetOffset ? smallerOffset : largerOffset; - - CGFloat firstOffset = [[self.snapToOffsets firstObject] floatValue]; - CGFloat lastOffset = [[self.snapToOffsets lastObject] floatValue]; - - // if scrolling after the last snap offset and snapping to the - // end of the list is disabled, then we allow free scrolling - if (!self.snapToEnd && targetOffset >= lastOffset) { - if (offsetAlongAxis >= lastOffset) { - // free scrolling - } else { - // snap to end - targetOffset = lastOffset; - } - } else if (!self.snapToStart && targetOffset <= firstOffset) { - if (offsetAlongAxis <= firstOffset) { - // free scrolling - } else { - // snap to beginning - targetOffset = firstOffset; - } - } else if (velocityAlongAxis > 0.0) { - targetOffset = largerOffset; - } else if (velocityAlongAxis < 0.0) { - targetOffset = smallerOffset; - } else { - targetOffset = nearestOffset; - } - - // Make sure the new offset isn't out of bounds - targetOffset = MIN(MAX(0, targetOffset), maximumOffset); - - // Set new targetContentOffset - if (isHorizontal) { - targetContentOffset->x = targetOffset; - } else { - targetContentOffset->y = targetOffset; - } - } else if (self.snapToInterval) { - // An alternative to enablePaging which allows setting custom stopping intervals, - // smaller than a full page size. Often seen in apps which feature horizonally - // scrolling items. snapToInterval does not enforce scrolling one interval at a time - // but guarantees that the scroll will stop at an interval point. - CGFloat snapToIntervalF = (CGFloat)self.snapToInterval; - - // Find which axis to snap - BOOL isHorizontal = [self isHorizontal:scrollView]; - - // What is the current offset? - CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y; - CGFloat targetContentOffsetAlongAxis = targetContentOffset->y; - if (isHorizontal) { - // Use current scroll offset to determine the next index to snap to when momentum disabled - targetContentOffsetAlongAxis = self.disableIntervalMomentum ? scrollView.contentOffset.x : targetContentOffset->x; - } else { - targetContentOffsetAlongAxis = self.disableIntervalMomentum ? scrollView.contentOffset.y : targetContentOffset->y; - } - - // Offset based on desired alignment - CGFloat frameLength = isHorizontal ? self.frame.size.width : self.frame.size.height; - CGFloat alignmentOffset = 0.0f; - if ([self.snapToAlignment isEqualToString:@"center"]) { - alignmentOffset = (frameLength * 0.5f) + (snapToIntervalF * 0.5f); - } else if ([self.snapToAlignment isEqualToString:@"end"]) { - alignmentOffset = frameLength; - } - - // Pick snap point based on direction and proximity - CGFloat fractionalIndex = (targetContentOffsetAlongAxis + alignmentOffset) / snapToIntervalF; - - NSInteger snapIndex = velocityAlongAxis > 0.0 ? ceil(fractionalIndex) - : velocityAlongAxis < 0.0 ? floor(fractionalIndex) - : round(fractionalIndex); - CGFloat newTargetContentOffset = (snapIndex * snapToIntervalF) - alignmentOffset; - - // Set new targetContentOffset - if (isHorizontal) { - targetContentOffset->x = newTargetContentOffset; - } else { - targetContentOffset->y = newTargetContentOffset; - } - } - - NSDictionary *userData = @{ - @"velocity" : @{@"x" : @(velocity.x), @"y" : @(velocity.y)}, - @"targetContentOffset" : @{@"x" : @(targetContentOffset->x), @"y" : @(targetContentOffset->y)} - }; - RCT_SEND_SCROLL_EVENT(onScrollEndDrag, userData); - RCT_FORWARD_SCROLL_EVENT( - scrollViewWillEndDragging : scrollView withVelocity : velocity targetContentOffset : targetContentOffset); -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndDragging : scrollView willDecelerate : decelerate); -} - -- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view -{ - RCT_SEND_SCROLL_EVENT(onScrollBeginDrag, nil); - RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginZooming : scrollView withView : view); -} - -- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale -{ - RCT_SEND_SCROLL_EVENT(onScrollEndDrag, nil); - RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndZooming : scrollView withView : view atScale : scale); -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - if (self.window == nil) { - // Check if the ScrollView was in motion - if (_scrollView.isDecelerating || !_scrollView.isTracking) { - // Trigger the onMomentumScrollEnd event manually - RCT_SEND_SCROLL_EVENT(onMomentumScrollEnd, nil); - // We can't use the RCT_FORWARD_SCROLL_EVENT here beacuse the `_cmd` parameter passed - // to `respondsToSelector` is the current method - so it will be `didMoveToWindow` - and not - // `scrollViewDidEndDecelerating` that is passed. - for (NSObject *scrollViewListener in _scrollListeners) { - if ([scrollViewListener respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) { - [scrollViewListener scrollViewDidEndDecelerating:_scrollView]; - } - } - } - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - // Fire a final scroll event - _allowNextScrollNoMatterWhat = YES; - [self scrollViewDidScroll:scrollView]; - - // Fire the end deceleration event - RCT_SEND_SCROLL_EVENT(onMomentumScrollEnd, nil); - RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndDecelerating : scrollView); -} - -- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView -{ - // Fire a final scroll event - _allowNextScrollNoMatterWhat = YES; - [self scrollViewDidScroll:scrollView]; - - // Fire the end deceleration event - RCT_SEND_SCROLL_EVENT(onMomentumScrollEnd, nil); - RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndScrollingAnimation : scrollView); -} - -- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView -{ - for (NSObject *scrollListener in _scrollListeners) { - if ([scrollListener respondsToSelector:_cmd] && ![scrollListener scrollViewShouldScrollToTop:scrollView]) { - return NO; - } - } - - if (self.inverted) { - [self scrollToEnd:YES]; - return NO; - } - - return YES; -} - -- (UIView *)viewForZoomingInScrollView:(__unused UIScrollView *)scrollView -{ - return _contentView; -} - -- (CGSize)_calculateViewportSize -{ - CGSize viewportSize = self.bounds.size; - if (_automaticallyAdjustContentInsets) { - UIEdgeInsets contentInsets = RCTContentInsets(self); - viewportSize = CGSizeMake( - self.bounds.size.width - contentInsets.left - contentInsets.right, - self.bounds.size.height - contentInsets.top - contentInsets.bottom); - } - return viewportSize; -} - -- (CGSize)contentSize -{ - return _contentView.frame.size; -} - -- (void)updateContentSizeIfNeeded -{ - CGSize contentSize = self.contentSize; - if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) { - _scrollView.contentSize = contentSize; - [self centerContentIfNeeded]; - } -} - -// maintainVisibleContentPosition is used to allow seamless loading of content from both ends of -// the scrollview without the visible content jumping in position. -- (void)setMaintainVisibleContentPosition:(NSDictionary *)maintainVisibleContentPosition -{ - if (maintainVisibleContentPosition != nil && _maintainVisibleContentPosition == nil) { - [_eventDispatcher.bridge.uiManager.observerCoordinator addObserver:self]; - } else if (maintainVisibleContentPosition == nil && _maintainVisibleContentPosition != nil) { - [_eventDispatcher.bridge.uiManager.observerCoordinator removeObserver:self]; - } - _maintainVisibleContentPosition = maintainVisibleContentPosition; -} - -#pragma mark - RCTUIManagerObserver - -- (void)uiManagerWillPerformMounting:(RCTUIManager *)manager -{ - RCTAssertUIManagerQueue(); - - [manager prependUIBlock:^( - __unused RCTUIManager *uiManager, __unused NSDictionary *viewRegistry) { - BOOL horz = [self isHorizontal:self->_scrollView]; - NSUInteger minIdx = [self->_maintainVisibleContentPosition[@"minIndexForVisible"] integerValue]; - for (NSUInteger ii = minIdx; ii < self->_contentView.subviews.count; ++ii) { - // Find the first partially or fully visible view. This must be done after we update the content offset - // or it will tend to grab rows that were made visible by the shift in position - UIView *subview = self->_contentView.subviews[ii]; - BOOL hasNewView = NO; - if (horz) { - CGFloat leftInset = self.inverted ? self->_scrollView.contentInset.right : self->_scrollView.contentInset.left; - CGFloat x = self->_scrollView.contentOffset.x + leftInset; - hasNewView = subview.frame.origin.x + subview.frame.size.width > x; - } else { - CGFloat bottomInset = - self.inverted ? self->_scrollView.contentInset.top : self->_scrollView.contentInset.bottom; - CGFloat y = self->_scrollView.contentOffset.y + bottomInset; - hasNewView = subview.frame.origin.y + subview.frame.size.height > y; - } - if (hasNewView || ii == self->_contentView.subviews.count - 1) { - self->_prevFirstVisibleFrame = subview.frame; - self->_firstVisibleView = subview; - break; - } - } - }]; - [manager addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary *viewRegistry) { - if (self->_maintainVisibleContentPosition == nil) { - return; // The prop might have changed in the previous UIBlocks, so need to abort here. - } - NSNumber *autoscrollThreshold = self->_maintainVisibleContentPosition[@"autoscrollToTopThreshold"]; - // TODO: detect and handle/ignore re-ordering - if ([self isHorizontal:self->_scrollView]) { - CGFloat deltaX = self->_firstVisibleView.frame.origin.x - self->_prevFirstVisibleFrame.origin.x; - if (ABS(deltaX) > 0.1) { - CGFloat leftInset = self.inverted ? self->_scrollView.contentInset.right : self->_scrollView.contentInset.left; - CGFloat x = self->_scrollView.contentOffset.x + leftInset; - self->_scrollView.contentOffset = - CGPointMake(self->_scrollView.contentOffset.x + deltaX, self->_scrollView.contentOffset.y); - if (autoscrollThreshold != nil) { - // If the offset WAS within the threshold of the start, animate to the start. - if (x <= [autoscrollThreshold integerValue]) { - [self scrollToOffset:CGPointMake(-leftInset, self->_scrollView.contentOffset.y) animated:YES]; - } - } - } - } else { - CGRect newFrame = self->_firstVisibleView.frame; - CGFloat deltaY = newFrame.origin.y - self->_prevFirstVisibleFrame.origin.y; - if (ABS(deltaY) > 0.1) { - CGFloat bottomInset = - self.inverted ? self->_scrollView.contentInset.top : self->_scrollView.contentInset.bottom; - CGFloat y = self->_scrollView.contentOffset.y + bottomInset; - self->_scrollView.contentOffset = - CGPointMake(self->_scrollView.contentOffset.x, self->_scrollView.contentOffset.y + deltaY); - if (autoscrollThreshold != nil) { - // If the offset WAS within the threshold of the start, animate to the start. - if (y <= [autoscrollThreshold integerValue]) { - [self scrollToOffset:CGPointMake(self->_scrollView.contentOffset.x, -bottomInset) animated:YES]; - } - } - } - } - }]; -} - -// Note: setting several properties of UIScrollView has the effect of -// resetting its contentOffset to {0, 0}. To prevent this, we generate -// setters here that will record the contentOffset beforehand, and -// restore it after the property has been set. - -#define RCT_SET_AND_PRESERVE_OFFSET(setter, getter, type) \ - -(void)setter : (type)value \ - { \ - CGPoint contentOffset = _scrollView.contentOffset; \ - [_scrollView setter:value]; \ - _scrollView.contentOffset = contentOffset; \ - } \ - -(type)getter \ - { \ - return [_scrollView getter]; \ - } - -RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, alwaysBounceHorizontal, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, alwaysBounceVertical, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setBounces, bounces, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, bouncesZoom, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, canCancelContentTouches, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, decelerationRate, CGFloat) -RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, isDirectionalLockEnabled, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, indicatorStyle, UIScrollViewIndicatorStyle) -RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, keyboardDismissMode, UIScrollViewKeyboardDismissMode) -RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, maximumZoomScale, CGFloat) -RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, minimumZoomScale, CGFloat) -RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, isScrollEnabled, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, isPagingEnabled, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, scrollsToTop, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, showsHorizontalScrollIndicator, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL) -RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat); - -- (void)setScrollIndicatorInsets:(UIEdgeInsets)value -{ - [_scrollView setScrollIndicatorInsets:value]; -} - -- (UIEdgeInsets)scrollIndicatorInsets -{ - UIEdgeInsets verticalScrollIndicatorInsets = [_scrollView verticalScrollIndicatorInsets]; - UIEdgeInsets horizontalScrollIndicatorInsets = [_scrollView horizontalScrollIndicatorInsets]; - - return UIEdgeInsetsMake( - verticalScrollIndicatorInsets.top, - horizontalScrollIndicatorInsets.left, - verticalScrollIndicatorInsets.bottom, - horizontalScrollIndicatorInsets.right); -} - -- (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjusts API_AVAILABLE(ios(13.0)) -{ - // `automaticallyAdjustsScrollIndicatorInsets` is available since iOS 13. - if ([_scrollView respondsToSelector:@selector(setAutomaticallyAdjustsScrollIndicatorInsets:)]) { - _scrollView.automaticallyAdjustsScrollIndicatorInsets = automaticallyAdjusts; - } -} - -- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior -{ - CGPoint contentOffset = _scrollView.contentOffset; - _scrollView.contentInsetAdjustmentBehavior = behavior; - _scrollView.contentOffset = contentOffset; -} - -- (void)sendScrollEventWithName:(NSString *)eventName - scrollView:(UIScrollView *)scrollView - userData:(NSDictionary *)userData -{ - if (![_lastEmittedEventName isEqualToString:eventName]) { - _coalescingKey++; - _lastEmittedEventName = [eventName copy]; - } - - CGPoint offset = scrollView.contentOffset; - if ([self reactLayoutDirection] == UIUserInterfaceLayoutDirectionRightToLeft) { - offset.x = scrollView.contentSize.width - scrollView.frame.size.width - offset.x; - } - - RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithEventName:eventName - reactTag:self.reactTag - scrollViewContentOffset:offset - scrollViewContentInset:scrollView.contentInset - scrollViewContentSize:scrollView.contentSize - scrollViewFrame:scrollView.frame - scrollViewZoomScale:scrollView.zoomScale - userData:userData - coalescingKey:_coalescingKey]; - [_eventDispatcher sendEvent:scrollEvent]; -} - -@end - -void RCTSendFakeScrollEvent(id eventDispatcher, NSNumber *reactTag) -{ - // Use the selector here in case the onScroll block property is ever renamed - NSString *eventName = NSStringFromSelector(@selector(onScroll)); - RCTScrollEvent *fakeScrollEvent = [[RCTScrollEvent alloc] initWithEventName:eventName - reactTag:reactTag - scrollViewContentOffset:CGPointZero - scrollViewContentInset:UIEdgeInsetsZero - scrollViewContentSize:CGSizeZero - scrollViewFrame:CGRectZero - scrollViewZoomScale:0 - userData:nil - coalescingKey:0]; - [eventDispatcher sendEvent:fakeScrollEvent]; -} - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.h b/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.h index bb5d7b0cea4..112f0c0ba57 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.h @@ -7,19 +7,3 @@ #import #import - -#ifndef RCT_REMOVE_LEGACY_ARCH - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTConvert(UIScrollView) - -+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json; - -@end - -__attribute__((deprecated("This API will be removed along with the legacy architecture."))) -@interface RCTScrollViewManager : RCTViewManager - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m index 985771805c5..7f14e791077 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m @@ -6,197 +6,3 @@ */ #import "RCTScrollViewManager.h" - -#ifndef RCT_REMOVE_LEGACY_ARCH - -#import "RCTBridge.h" -#import "RCTScrollView.h" -#import "RCTShadowView.h" -#import "RCTUIManager.h" - -@implementation RCTConvert (UIScrollView) - -RCT_ENUM_CONVERTER( - UIScrollViewKeyboardDismissMode, - (@{ - @"none" : @(UIScrollViewKeyboardDismissModeNone), - @"on-drag" : @(UIScrollViewKeyboardDismissModeOnDrag), - @"interactive" : @(UIScrollViewKeyboardDismissModeInteractive), - // Backwards compatibility - @"onDrag" : @(UIScrollViewKeyboardDismissModeOnDrag), - }), - UIScrollViewKeyboardDismissModeNone, - integerValue) - -RCT_ENUM_CONVERTER( - UIScrollViewIndicatorStyle, - (@{ - @"default" : @(UIScrollViewIndicatorStyleDefault), - @"black" : @(UIScrollViewIndicatorStyleBlack), - @"white" : @(UIScrollViewIndicatorStyleWhite), - }), - UIScrollViewIndicatorStyleDefault, - integerValue) - -RCT_ENUM_CONVERTER( - UIScrollViewContentInsetAdjustmentBehavior, - (@{ - @"automatic" : @(UIScrollViewContentInsetAdjustmentAutomatic), - @"scrollableAxes" : @(UIScrollViewContentInsetAdjustmentScrollableAxes), - @"never" : @(UIScrollViewContentInsetAdjustmentNever), - @"always" : @(UIScrollViewContentInsetAdjustmentAlways), - }), - UIScrollViewContentInsetAdjustmentNever, - integerValue) - -@end - -@implementation RCTScrollViewManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - return [[RCTScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; -} - -RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal, BOOL) -RCT_EXPORT_VIEW_PROPERTY(alwaysBounceVertical, BOOL) -RCT_EXPORT_VIEW_PROPERTY(bounces, BOOL) -RCT_EXPORT_VIEW_PROPERTY(bouncesZoom, BOOL) -RCT_EXPORT_VIEW_PROPERTY(canCancelContentTouches, BOOL) -RCT_EXPORT_VIEW_PROPERTY(centerContent, BOOL) -RCT_EXPORT_VIEW_PROPERTY(maintainVisibleContentPosition, NSDictionary) -RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) -RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustKeyboardInsets, BOOL) -RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(endDraggingSensitivityMultiplier, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(indicatorStyle, UIScrollViewIndicatorStyle) -RCT_EXPORT_VIEW_PROPERTY(keyboardDismissMode, UIScrollViewKeyboardDismissMode) -RCT_EXPORT_VIEW_PROPERTY(maximumZoomScale, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(minimumZoomScale, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL) -RCT_REMAP_VIEW_PROPERTY(pinchGestureEnabled, scrollView.pinchGestureEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL) -RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL) -RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL) -RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval) -RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(verticalScrollIndicatorInsets, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) -RCT_EXPORT_VIEW_PROPERTY(disableIntervalMomentum, BOOL) -RCT_EXPORT_VIEW_PROPERTY(snapToOffsets, NSArray) -RCT_EXPORT_VIEW_PROPERTY(snapToStart, BOOL) -RCT_EXPORT_VIEW_PROPERTY(snapToEnd, BOOL) -RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString) -RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint) -RCT_EXPORT_VIEW_PROPERTY(onScrollBeginDrag, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onScrollToTop, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onScrollEndDrag, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollBegin, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(inverted, BOOL) -RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustsScrollIndicatorInsets, BOOL) -RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior) - -// overflow is used both in css-layout as well as by react-native. In css-layout -// we always want to treat overflow as scroll but depending on what the overflow -// is set to from js we want to clip drawing or not. This piece of code ensures -// that css-layout is always treating the contents of a scroll container as -// overflow: 'scroll'. -RCT_CUSTOM_SHADOW_PROPERTY(overflow, YGOverflow, RCTShadowView) -{ -#pragma unused(json) - view.overflow = YGOverflowScroll; -} - -RCT_EXPORT_METHOD(getContentSize : (nonnull NSNumber *)reactTag callback : (RCTResponseSenderBlock)callback) -{ - [self.bridge.uiManager - addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTScrollView *view = viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[RCTScrollView class]]) { - RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); - return; - } - - CGSize size = view.scrollView.contentSize; - callback(@[ @{@"width" : @(size.width), @"height" : @(size.height)} ]); - }]; -} - -RCT_EXPORT_METHOD( - scrollTo : (nonnull NSNumber *)reactTag offsetX : (CGFloat)x offsetY : (CGFloat)y animated : (BOOL)animated) -{ - [self.bridge.uiManager - addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[reactTag]; - if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - [(id)view scrollToOffset:(CGPoint){x, y} animated:animated]; - } else { - RCTLogError( - @"tried to scrollTo: on non-RCTScrollableProtocol view %@ " - "with tag #%@", - view, - reactTag); - } - }]; -} - -RCT_EXPORT_METHOD(scrollToEnd : (nonnull NSNumber *)reactTag animated : (BOOL)animated) -{ - [self.bridge.uiManager - addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[reactTag]; - if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - [(id)view scrollToEnd:animated]; - } else { - RCTLogError( - @"tried to scrollTo: on non-RCTScrollableProtocol view %@ " - "with tag #%@", - view, - reactTag); - } - }]; -} - -RCT_EXPORT_METHOD(zoomToRect : (nonnull NSNumber *)reactTag withRect : (CGRect)rect animated : (BOOL)animated) -{ - [self.bridge.uiManager - addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = viewRegistry[reactTag]; - if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - [(id)view zoomToRect:rect animated:animated]; - } else { - RCTLogError( - @"tried to zoomToRect: on non-RCTScrollableProtocol view %@ " - "with tag #%@", - view, - reactTag); - } - }]; -} - -RCT_EXPORT_METHOD(flashScrollIndicators : (nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager - addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTScrollView *view = viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[RCTScrollView class]]) { - RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); - return; - } - - [view.scrollView flashScrollIndicators]; - }]; -} - -@end - -#endif // RCT_REMOVE_LEGACY_ARCH