Skip to content

Commit

Permalink
Add visionOS support (microsoft#2019)
Browse files Browse the repository at this point in the history
* Remove TARGET_OS_UIKITFORMAC macros (facebook#42278)

Summary:
There seems to be a lot of `TARGET_OS_UIKITFORMAC` macro in React Native that don't need to be there. Let's remove them.

First off, what is `TARGET_OS_UIKITFORMAC` targeting? You might think it's [Mac Catalyst](https://developer.apple.com/mac-catalyst/), if you look at the [commit](facebook@3724810) introducing the ifdefs.  However.. that doesn't seem right because `TARGET_OS_MACCATALYST` exists, and is used elsewhere in the codebase. In fact, if you look at this handy comment inside `TargetConditionals.h` (the file that defines all these conditionals), `TARGET_OS_UIKITFORMAC` is not even on there!

```
/*
 *  TARGET_OS_*
 *
 *  These conditionals specify in which Operating System the generated code will
 *  run.  Indention is used to show which conditionals are evolutionary subclasses.
 *
 *  The MAC/WIN32/UNIX conditionals are mutually exclusive.
 *  The IOS/TV/WATCH/VISION conditionals are mutually exclusive.
 *
 *    TARGET_OS_WIN32              - Generated code will run on WIN32 API
 *    TARGET_OS_WINDOWS            - Generated code will run on Windows
 *    TARGET_OS_UNIX               - Generated code will run on some Unix (not macOS)
 *    TARGET_OS_LINUX              - Generated code will run on Linux
 *    TARGET_OS_MAC                - Generated code will run on a variant of macOS
 *      TARGET_OS_OSX                - Generated code will run on macOS
 *      TARGET_OS_IPHONE             - Generated code will run on a variant of iOS (firmware, devices, simulator)
 *        TARGET_OS_IOS                - Generated code will run on iOS
 *          TARGET_OS_MACCATALYST        - Generated code will run on macOS
 *        TARGET_OS_TV                 - Generated code will run on tvOS
 *        TARGET_OS_WATCH              - Generated code will run on watchOS
 *        TARGET_OS_VISION             - Generated code will run on visionOS
 *        TARGET_OS_BRIDGE             - Generated code will run on bridge devices
 *      TARGET_OS_SIMULATOR          - Generated code will run on an iOS, tvOS, watchOS, or visionOS simulator
 *      TARGET_OS_DRIVERKIT          - Generated code will run on macOS, iOS, tvOS, watchOS, or visionOS
 *
 *    TARGET_OS_EMBEDDED           - DEPRECATED: Use TARGET_OS_IPHONE and/or TARGET_OS_SIMULATOR instead
 *    TARGET_IPHONE_SIMULATOR      - DEPRECATED: Same as TARGET_OS_SIMULATOR
 *    TARGET_OS_NANO               - DEPRECATED: Same as TARGET_OS_WATCH
 *
 *    +--------------------------------------------------------------------------------------+
 *    |                                    TARGET_OS_MAC                                     |
 *    | +-----+ +------------------------------------------------------------+ +-----------+ |
 *    | |     | |                  TARGET_OS_IPHONE                          | |           | |
 *    | |     | | +-----------------+ +----+ +-------+ +--------+ +--------+ | |           | |
 *    | |     | | |       IOS       | |    | |       | |        | |        | | |           | |
 *    | | OSX | | | +-------------+ | | TV | | WATCH | | BRIDGE | | VISION | | | DRIVERKIT | |
 *    | |     | | | | MACCATALYST | | |    | |       | |        | |        | | |           | |
 *    | |     | | | +-------------+ | |    | |       | |        | |        | | |           | |
 *    | |     | | +-----------------+ +----+ +-------+ +--------+ +--------+ | |           | |
 *    | +-----+ +------------------------------------------------------------+ +-----------+ |
 *    +--------------------------------------------------------------------------------------+
 */
```

Going even deeper into `TargetConditionals.h`, you will see `TARGET_OS_UIKITFORMAC` defined... and it's always 1 when `TARGET_OS_MACCATALYST` is 1, making it feel even more redundant. My current conclusion is it's either another variant of Mac Catalyst (the one where they just run unmodified UIKit maybe..), or it's an older macro back from when Catalyst was still experimental.

Either way, it's pretty obvious nobody is running or testing this codepath, and it adds bloat, especially to React Native macOS where we have extra ifdef blocks for macOS support (and eventually visionOS support). Let's remove it.

Another change I made while we're here:
I've seen this lingering TODO to replace setTargetRect:InView: / setMenuVisible:animated: (deprecated as of iOS 13, below our minimum OS requirement) with showMenuFromView (deprecated as of iOS 16, in line with the availability check). Let's just.... do that?

[IOS] [REMOVED] - Remove TARGET_OS_UIKITFORMAC macros

Pull Request resolved: facebook#42278

Test Plan:
RNTester with Mac Catalyst still compiles:
![Screenshot 2024-01-15 at 12 26 03 AM](https://github.com/facebook/react-native/assets/6722175/015bd37d-f536-43c7-9586-96187cdbd013)

Reviewed By: cipolleschi

Differential Revision: D52780690

Pulled By: sammy-SC

fbshipit-source-id: df6a333e8e15f79de0ce6f538ebd73b92698dcb6

* Remove an early return to suppress a deprecated API warning for `UIMenuController` (facebook#42277)

Summary:
`UIMenuController` is deprecated as of iOS 16. facebook@e08a197 migrated a usage into an `available` check. However, it does not properly fall back to the deprecated API in the "else" block of the availability check, instead it uses an early return. It seems this means Xcode still sees the API as used, and spits out a deprecated warning. Let's just refactor the code so we don't have that anymore.

[IOS] [FIXED] -  Remove an early return to suppress a deprecated API warning for `UIMenuController`

Pull Request resolved: facebook#42277

Test Plan: CI should pass.

Reviewed By: cipolleschi

Differential Revision: D52785488

Pulled By: sammy-SC

fbshipit-source-id: 0b47e8aa8d7c94728e3d68332fbb8f97f8ded34e

* Native changes for visionOS
  • Loading branch information
Saadnajmi committed Jan 28, 2024
1 parent 0b66dea commit 7243805
Show file tree
Hide file tree
Showing 36 changed files with 240 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification
rootView = [self createRootViewWithBridge:self.bridge moduleName:self.moduleName initProps:initProps];
}
#if !TARGET_OS_OSX // [macOS]
#if !TARGET_OS_VISION // [visionOS]
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
#else
self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 1280, 720)];
#endif // [visionOS]
UIViewController *rootViewController = [self createRootViewController];
[self setRootView:rootView toRootViewController:rootViewController];
self.window.rootViewController = rootViewController;
Expand All @@ -138,7 +142,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification

return YES;
#else // [macOS
NSRect frame = NSMakeRect(0,0,1024,768);
NSRect frame = NSMakeRect(0,0,1280,720);
self.window = [[NSWindow alloc] initWithContentRect:NSZeroRect
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable
backing:NSBackingStoreBuffered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,10 @@ - (BOOL)paused

- (void)displayDidRefresh:(CADisplayLink *)displayLink
{
#if TARGET_OS_UIKITFORMAC
// TODO: `displayLink.frameInterval` is not available on UIKitForMac
NSTimeInterval durationToNextRefresh = displayLink.duration;
#else
// displaylink.duration -- time interval between frames, assuming maximumFramesPerSecond
// displayLink.preferredFramesPerSecond (>= iOS 10) -- Set to 30 for displayDidRefresh to be called at 30 fps
// durationToNextRefresh -- Time interval to the next time displayDidRefresh is called
NSTimeInterval durationToNextRefresh = displayLink.targetTimestamp - displayLink.timestamp;
#endif
NSUInteger totalFrameCount = self.totalFrameCount;
NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ Pod::Spec.new do |s|
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')
}
s.ios.frameworks = "MobileCoreServices" # [macOS] Restrict to iOS
# [macOS MobileCoreServices Not available on macOS
s.ios.frameworks = "MobileCoreServices"
s.visionos.frameworks = "MobileCoreServices"
# macOS]

s.dependency "RCT-Folly", folly_version
s.dependency "RCTTypeSafety"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ extern NSString *const RCTRemoteNotificationReceived;
typedef void (^RCTRemoteNotificationCallback)(UIBackgroundFetchResult result);
#endif // [macOS]

#if !TARGET_OS_UIKITFORMAC
+ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+ (void)didReceiveRemoteNotification:(NSDictionary *)notification;
#if !TARGET_OS_OSX // [macOS]
+ (void)didReceiveRemoteNotification:(NSDictionary *)notification
fetchCompletionHandler:(RCTRemoteNotificationCallback)completionHandler;
#if TARGET_OS_IOS // [visionOS]
+ (void)didReceiveLocalNotification:(UILocalNotification *)notification;
#endif // [visionOS]
#endif // [macOS]
#if TARGET_OS_OSX // [macOS
+ (void)didReceiveUserNotification:(NSUserNotification *)notification;
#endif // macOS]
+ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
#endif

@end
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

static NSString *const kErrorUnableToRequestPermissions = @"E_UNABLE_TO_REQUEST_PERMISSIONS";

#if !TARGET_OS_UIKITFORMAC

@interface RCTPushNotificationManager () <NativePushNotificationManagerIOSSpec>
@property (nonatomic, strong) NSMutableDictionary *remoteNotificationCallbacks;
@end
Expand Down Expand Up @@ -97,16 +95,10 @@ @implementation RCTConvert (UIBackgroundFetchResult)

@end
#endif // [macOS]
#else
@interface RCTPushNotificationManager () <NativePushNotificationManagerIOSSpec>
@end
#endif // TARGET_OS_UIKITFORMAC

@implementation RCTPushNotificationManager

#if !TARGET_OS_UIKITFORMAC

#if !TARGET_OS_OSX // [macOS]
#if TARGET_OS_IOS // [macOS] [visionOS]
/** DEPRECATED. UILocalNotification was deprecated in iOS 10. Please don't add new callsites. */
static NSDictionary *RCTFormatLocalNotification(UILocalNotification *notification)
{
Expand All @@ -126,7 +118,8 @@ @implementation RCTPushNotificationManager
formattedLocalNotification[@"remote"] = @NO;
return formattedLocalNotification;
}
#else // [macOS
#endif // [macOS] [visionOS]
#if TARGET_OS_OSX // [macOS
static NSDictionary *RCTFormatUserNotification(NSUserNotification *notification)
{
NSMutableDictionary *formattedUserNotification = [NSMutableDictionary dictionary];
Expand Down Expand Up @@ -198,16 +191,13 @@ @implementation RCTPushNotificationManager
return [formatter stringFromDate:date];
}

#endif // TARGET_OS_UIKITFORMAC

RCT_EXPORT_MODULE()

- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}

#if !TARGET_OS_UIKITFORMAC
- (void)startObserving
{
[[NSNotificationCenter defaultCenter] addObserver:self
Expand Down Expand Up @@ -282,14 +272,15 @@ + (void)didReceiveRemoteNotification:(NSDictionary *)notification
}
#endif // [macOS]

#if !TARGET_OS_OSX // [macOS]
#if TARGET_OS_IOS // [macOS] [visionOS]
+ (void)didReceiveLocalNotification:(UILocalNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:kLocalNotificationReceived
object:self
userInfo:RCTFormatLocalNotification(notification)];
}
#else // [macOS
#endif // [macOS] [visionOS]
#if TARGET_OS_OSX // [macOS
+ (void)didReceiveUserNotification:(NSUserNotification *)notification
{
NSString *notificationName = notification.isRemote ? RCTRemoteNotificationReceived : kLocalNotificationReceived;
Expand Down Expand Up @@ -568,7 +559,7 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification
: (RCTPromiseResolveBlock)resolve reject
: (__unused RCTPromiseRejectBlock)reject)
{
#if !TARGET_OS_OSX // [macOS]
#if TARGET_OS_IOS // [macOS] [visionOS]
NSMutableDictionary<NSString *, id> *initialNotification =
[self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] mutableCopy];

Expand All @@ -583,7 +574,8 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification
} else {
resolve((id)kCFNull);
}
#else // [macOS
#endif // [macOS] [visionOS]
#if TARGET_OS_OSX // [macOS
NSUserNotification *initialNotification = self.bridge.launchOptions[NSApplicationLaunchUserNotificationKey];
if (initialNotification) {
resolve(RCTFormatUserNotification(initialNotification));
Expand Down Expand Up @@ -638,100 +630,6 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification
}];
}

#else // TARGET_OS_UIKITFORMAC

RCT_EXPORT_METHOD(onFinishRemoteNotification : (NSString *)notificationId fetchResult : (NSString *)fetchResult)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(setApplicationIconBadgeNumber : (double)number)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(getApplicationIconBadgeNumber : (RCTResponseSenderBlock)callback)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(requestPermissions
: (JS::NativePushNotificationManagerIOS::SpecRequestPermissionsPermission &)permissions resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(abandonPermissions)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(checkPermissions : (RCTResponseSenderBlock)callback)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(presentLocalNotification : (JS::NativePushNotificationManagerIOS::Notification &)notification)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(scheduleLocalNotification : (JS::NativePushNotificationManagerIOS::Notification &)notification)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(cancelAllLocalNotifications)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(cancelLocalNotifications : (NSDictionary<NSString *, id> *)userInfo)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(getInitialNotification
: (RCTPromiseResolveBlock)resolve reject
: (__unused RCTPromiseRejectBlock)reject)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(getScheduledLocalNotifications : (RCTResponseSenderBlock)callback)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(removeAllDeliveredNotifications)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(removeDeliveredNotifications : (NSArray<NSString *> *)identifiers)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(getDeliveredNotifications : (RCTResponseSenderBlock)callback)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

RCT_EXPORT_METHOD(getAuthorizationStatus : (RCTResponseSenderBlock)callback)
{
RCTLogError(@"Not implemented: %@", NSStringFromSelector(_cmd));
}

- (NSArray<NSString *> *)supportedEvents
{
return @[];
}

#endif // TARGET_OS_UIKITFORMAC

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
Expand Down
5 changes: 4 additions & 1 deletion packages/react-native/Libraries/Text/React-RCTText.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ Pod::Spec.new do |s|
s.ios.exclude_files = "**/macOS/*" # [macOS]
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTText"
s.ios.framework = ["MobileCoreServices"] # [macOS] Restrict to iOS
# [macOS MobileCoreServices Not available on macOS
s.ios.frameworks = "MobileCoreServices"
s.visionos.frameworks = "MobileCoreServices"
# macOS]
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20" }

s.dependency "Yoga"
Expand Down
22 changes: 7 additions & 15 deletions packages/react-native/Libraries/Text/Text/RCTTextView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -373,29 +373,21 @@ - (void)disableContextMenu

- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
#if !TARGET_OS_UIKITFORMAC
if (@available(iOS 16.0, *)) {
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];
}
return;
}
// TODO: Adopt showMenuFromRect (necessary for UIKitForMac)
UIMenuController *menuController = [UIMenuController sharedMenuController];
} else {
UIMenuController *menuController = [UIMenuController sharedMenuController];

if (menuController.isMenuVisible) {
return;
}
if (menuController.isMenuVisible) {
return;
}

if (!self.isFirstResponder) {
[self becomeFirstResponder];
[menuController showMenuFromView:self rect:self.bounds];
}

[menuController setTargetRect:self.bounds inView:self];
[menuController setMenuVisible:YES animated:YES];
#endif
}
#else // [macOS

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@ - (void)didMoveToWindow

#pragma mark - Custom Input Accessory View

#if TARGET_OS_IOS // [macOS] [visionOS]
- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
if ([changedProps containsObject:@"inputAccessoryViewID"] && self.inputAccessoryViewID) {
Expand All @@ -862,7 +863,6 @@ - (void)didSetProps:(NSArray<NSString *> *)changedProps

- (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID
{
#if !TARGET_OS_OSX // [macOS]
__weak RCTBaseTextInputView *weakSelf = self;
[_bridge.uiManager rootViewForReactTag:self.reactTag
withCompletion:^(UIView *rootView) {
Expand All @@ -877,12 +877,10 @@ - (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID
}
}
}];
#endif // [macOS]
}

- (void)setDefaultInputAccessoryView
{
#if !TARGET_OS_OSX // [macOS]
UIView<RCTBackedTextInputViewProtocol> *textInputView = self.backedTextInputView;
UIKeyboardType keyboardType = textInputView.keyboardType;

Expand Down Expand Up @@ -914,10 +912,8 @@ - (void)setDefaultInputAccessoryView
textInputView.inputAccessoryView = nil;
}
[self reloadInputViewsIfNecessary];
#endif // [macOS]
}

#if !TARGET_OS_OSX // [macOS]
- (void)reloadInputViewsIfNecessary
{
// We have to call `reloadInputViews` for focused text inputs to update an accessory view.
Expand All @@ -934,7 +930,7 @@ - (void)handleInputAccessoryDoneButton
[self.backedTextInputView endEditing:YES];
}
}
#endif // [macOS]
#endif // [macOS] [visionOS]

// [macOS

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

// [macOS]

#if TARGET_OS_OSX
#define RCT_SUBCLASS_SECURETEXTFIELD 1
#endif

#include <React/RCTUITextField.h>

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

// [macOS]

#if TARGET_OS_OSX
#define RCT_SUBCLASS_SECURETEXTFIELD 1
#endif

#include "../RCTUITextField.mm"
2 changes: 2 additions & 0 deletions packages/react-native/React/Base/RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,10 @@ + (UIKeyboardType)UIKeyboardType:(id)json RCT_DYNAMIC
(@{
@"default" : @(UIBarStyleDefault),
@"black" : @(UIBarStyleBlack),
#if !TARGET_OS_VISION // [visionOS]
@"blackOpaque" : @(UIBarStyleBlackOpaque),
@"blackTranslucent" : @(UIBarStyleBlackTranslucent),
#endif // [visionOS]
}),
UIBarStyleDefault,
integerValue)
Expand Down
Loading

0 comments on commit 7243805

Please sign in to comment.