diff --git a/packages/react-native/Libraries/Text/Text/RCTTextView.mm b/packages/react-native/Libraries/Text/Text/RCTTextView.mm index 4e5afc435ec77d..3f47a3a23b8787 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextView.mm @@ -37,11 +37,18 @@ - (BOOL)canBecomeKeyView @end -@interface RCTTextView () -@end +#endif // macOS] + +#if !TARGET_OS_OSX // [macOS] +@interface RCTTextView () +@property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0)); +#else // [macOS +@interface RCTTextView () #endif // macOS] +@end + #import @implementation RCTTextView { @@ -351,6 +358,10 @@ - (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]; } @@ -362,8 +373,16 @@ - (void)disableContextMenu - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture { - // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) #if !TARGET_OS_UIKITFORMAC + if (@available(iOS 16.0, *)) { + CGPoint location = [gesture locationInView:self]; + UIEditMenuConfiguration *config = [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:location]; + if (_editMenuInteraction) { + [_editMenuInteraction presentEditMenuWithConfiguration:config]; + } + return; + } + // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) UIMenuController *menuController = [UIMenuController sharedMenuController]; if (menuController.isMenuVisible) { diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m index 923d6a277ad0d4..648bceacfd841b 100644 --- a/packages/react-native/React/Base/RCTUtils.m +++ b/packages/react-native/React/Base/RCTUtils.m @@ -305,14 +305,14 @@ static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, disp void RCTComputeScreenScale(void) { dispatch_once(&onceTokenScreenScale, ^{ - screenScale = [UIScreen mainScreen].scale; + screenScale = [UITraitCollection currentTraitCollection].displayScale; }); } CGFloat RCTScreenScale(void) { RCTUnsafeExecuteOnMainQueueOnceSync(&onceTokenScreenScale, ^{ - screenScale = [UIScreen mainScreen].scale; + screenScale = [UITraitCollection currentTraitCollection].displayScale; }); return screenScale; @@ -600,12 +600,20 @@ BOOL RCTRunningInAppExtension(void) return nil; } - // TODO: replace with a more robust solution - for (UIWindow *window in RCTSharedApplication().windows) { - if (window.keyWindow) { - return window; + for (UIScene *scene in RCTSharedApplication().connectedScenes) { + if (scene.activationState != UISceneActivationStateForegroundActive || + ![scene isKindOfClass:[UIWindowScene class]]) { + continue; + } + UIWindowScene *windowScene = (UIWindowScene *)scene; + + for (UIWindow *window in windowScene.windows) { + if (window.isKeyWindow) { + return window; + } } } + return nil; #else // [macOS return [NSApp keyWindow]; @@ -634,12 +642,10 @@ BOOL RCTForceTouchAvailable(void) static BOOL forceSupported; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - forceSupported = - [UITraitCollection class] && [UITraitCollection instancesRespondToSelector:@selector(forceTouchCapability)]; + forceSupported = [UITraitCollection currentTraitCollection].forceTouchCapability == UIForceTouchCapabilityAvailable; }); - return forceSupported && - (RCTKeyWindow() ?: [UIView new]).traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable; + return forceSupported; } #endif // [macOS] diff --git a/packages/react-native/React/CoreModules/RCTAlertController.mm b/packages/react-native/React/CoreModules/RCTAlertController.mm index 698d7d1ae7856e..7999a937e697bd 100644 --- a/packages/react-native/React/CoreModules/RCTAlertController.mm +++ b/packages/react-native/React/CoreModules/RCTAlertController.mm @@ -23,17 +23,7 @@ @implementation RCTAlertController - (UIWindow *)alertWindow { if (_alertWindow == nil) { - _alertWindow = [self getUIWindowFromScene]; - - if (_alertWindow == nil) { - UIWindow *keyWindow = RCTSharedApplication().keyWindow; - if (keyWindow) { - _alertWindow = [[UIWindow alloc] initWithFrame:keyWindow.bounds]; - } else { - // keyWindow is nil, so we cannot create and initialize _alertWindow - NSLog(@"Unable to create alert window: keyWindow is nil"); - } - } + _alertWindow = [[UIWindow alloc] initWithWindowScene:RCTKeyWindow().windowScene]; if (_alertWindow) { _alertWindow.rootViewController = [UIViewController new]; @@ -67,18 +57,6 @@ - (void)hide _alertWindow = nil; } - -- (UIWindow *)getUIWindowFromScene -{ - for (UIScene *scene in RCTSharedApplication().connectedScenes) { - if (scene.activationState == UISceneActivationStateForegroundActive && - [scene isKindOfClass:[UIWindowScene class]]) { - return [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene]; - } - } - - return nil; -} #endif // [macOS] @end diff --git a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm index 3e2dcd83436b0e..5895b756cea3ce 100644 --- a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm +++ b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm @@ -120,15 +120,15 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo dispatch_async(dispatch_get_main_queue(), ^{ self->_showDate = [NSDate date]; + if (!self->_window && !RCTRunningInTestEnvironment()) { #if !TARGET_OS_OSX // [macOS] - CGSize screenSize = [UIScreen mainScreen].bounds.size; + UIWindow *window = RCTKeyWindow(); + CGFloat windowWidth = window.bounds.size.width; - UIWindow *window = RCTSharedApplication().keyWindow; - self->_window = - [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, window.safeAreaInsets.top + 10)]; - self->_label = - [[UILabel alloc] initWithFrame:CGRectMake(0, window.safeAreaInsets.top - 10, screenSize.width, 20)]; + self->_window = [[UIWindow alloc] initWithWindowScene:window.windowScene]; + self->_window.frame = CGRectMake(0, 0, windowWidth, window.safeAreaInsets.top + 10); + self->_label = [[UILabel alloc] initWithFrame:CGRectMake(0, window.safeAreaInsets.top - 10, windowWidth, 20)]; [self->_window addSubview:self->_label]; self->_window.windowLevel = UIWindowLevelStatusBar + 1; @@ -164,12 +164,10 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo self->_window.backgroundColor = backgroundColor; self->_window.hidden = NO; - - UIWindowScene *scene = (UIWindowScene *)RCTSharedApplication().connectedScenes.anyObject; - self->_window.windowScene = scene; #else // [macOS self->_label.stringValue = message; self->_label.textColor = color; + self->_label.backgroundColor = backgroundColor; [self->_window orderFront:nil]; #endif // macOS] diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index e2db81977263a3..8a20e5326707b0 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -27,6 +27,14 @@ using namespace facebook::react; +#if !TARGET_OS_OSX // [macOS] +@interface RCTParagraphComponentView () + +@property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0)); + +@end +#endif // [macOS] + @implementation RCTParagraphComponentView { ParagraphShadowNode::ConcreteState::Shared _state; ParagraphAttributes _paragraphAttributes; @@ -223,19 +231,36 @@ - (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 { - // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) #if !TARGET_OS_UIKITFORMAC + if (@available(iOS 16.0, *)) { + CGPoint location = [gesture locationInView:self]; + UIEditMenuConfiguration *config = [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:location]; + if (_editMenuInteraction) { + [_editMenuInteraction presentEditMenuWithConfiguration:config]; + } + return; + } + // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) UIMenuController *menuController = [UIMenuController sharedMenuController]; if (menuController.isMenuVisible) { diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index a382fe0da93e1b..d1340d92eda18d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -263,7 +263,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (oldViewProps.shouldRasterize != newViewProps.shouldRasterize) { self.layer.shouldRasterize = newViewProps.shouldRasterize; #if !TARGET_OS_OSX // [macOS] - self.layer.rasterizationScale = newViewProps.shouldRasterize ? [UIScreen mainScreen].scale : 1.0; + self.layer.rasterizationScale = newViewProps.shouldRasterize ? self.traitCollection.displayScale : 1.0; #else // [macOS self.layer.rasterizationScale = 1.0; #endif // macOS] diff --git a/packages/react-native/React/UIUtils/RCTUIUtils.m b/packages/react-native/React/UIUtils/RCTUIUtils.m index 8d6ebc0ea5c2f4..c5fe036be9f593 100644 --- a/packages/react-native/React/UIUtils/RCTUIUtils.m +++ b/packages/react-native/React/UIUtils/RCTUIUtils.m @@ -15,8 +15,7 @@ RCTDimensions RCTGetDimensions(CGFloat fontScale) UIScreen *mainScreen = UIScreen.mainScreen; CGSize screenSize = mainScreen.bounds.size; - UIView *mainWindow; - mainWindow = RCTKeyWindow(); + UIView *mainWindow = RCTKeyWindow(); // We fallback to screen size if a key window is not found. CGSize windowSize = mainWindow ? mainWindow.bounds.size : screenSize; #else // [macOS diff --git a/packages/react-native/React/Views/RCTViewManager.m b/packages/react-native/React/Views/RCTViewManager.m index 4063fbbc2908e9..53c82b94775669 100644 --- a/packages/react-native/React/Views/RCTViewManager.m +++ b/packages/react-native/React/Views/RCTViewManager.m @@ -277,7 +277,7 @@ - (RCTShadowView *)shadowView { view.layer.shouldRasterize = json ? [RCTConvert BOOL:json] : defaultView.layer.shouldRasterize; view.layer.rasterizationScale = - view.layer.shouldRasterize ? [UIScreen mainScreen].scale : defaultView.layer.rasterizationScale; + view.layer.shouldRasterize ? view.traitCollection.displayScale : defaultView.layer.rasterizationScale; } #endif // [macOS] diff --git a/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m b/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m index 01db01bfa2112e..ec4cd1cb835797 100644 --- a/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m +++ b/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m @@ -238,14 +238,15 @@ - (NSString *)_fileNameForSelector:(SEL)selector if (0 < identifier.length) { fileName = [fileName stringByAppendingFormat:@"_%@", identifier]; } - CGFloat scale; // [macOS -#if !TARGET_OS_OSX - scale = [[UIScreen mainScreen] scale]; -#else - scale = [[NSScreen mainScreen] backingScaleFactor]; -#endif - if (scale > 1.0) { // macOS] +#if !TARGET_OS_OSX // [macOS] + UITraitCollection *currentTraitCollection = [UITraitCollection currentTraitCollection]; + if (currentTraitCollection.displayScale > 1.0) { + fileName = [fileName stringByAppendingFormat:@"@%.fx", currentTraitCollection.displayScale]; +#else // [macOS + CGFloat scale = [[NSScreen mainScreen] backingScaleFactor]; + if (scale > 1.0) { fileName = [fileName stringByAppendingFormat:@"@%.fx", scale]; +#endif // macOS] } fileName = [fileName stringByAppendingPathExtension:@"png"]; return fileName; diff --git a/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m b/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m index 964efed23099cb..9aa90fa83c6e0d 100644 --- a/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m +++ b/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m @@ -45,14 +45,13 @@ - (BOOL)compareWithImage:(UIImage *)image (CGBitmapInfo)kCGImageAlphaPremultipliedLast); #if !TARGET_OS_OSX // [macOS] - CGFloat scaleFactor = [UIScreen mainScreen].scale; + CGFloat scaleFactor = [UITraitCollection currentTraitCollection].displayScale; #else // [macOS // The compareWithImage: method is used for integration test snapshot image comparison. // The _snapshotView: method that creates snapshot images that are *not* scaled for the screen. // By not using the screen scale factor in this method the test results are machine independent. CGFloat scaleFactor = 1; #endif // macOS] - CGContextScaleCTM(referenceImageContext, scaleFactor, scaleFactor); CGContextScaleCTM(imageContext, scaleFactor, scaleFactor);