diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index 34a791520..dbaa9affd 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -43,6 +43,11 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options failureCallback:(__unused RCTResponseSenderBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) { + if (RCTRunningInAppExtension()) { + RCTLogError(@"Unable to show action sheet from app extension"); + return; + } + UIActionSheet *actionSheet = [UIActionSheet new]; actionSheet.title = options[@"title"]; @@ -62,7 +67,7 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options _callbacks[RCTKeyForInstance(actionSheet)] = successCallback; - UIWindow *appWindow = [UIApplication sharedApplication].delegate.window; + UIWindow *appWindow = RCTSharedApplication().delegate.window; if (appWindow == nil) { RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options); return; @@ -87,8 +92,13 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options failureCallback(@[@"No `url` or `message` to share"]); return; } + if (RCTRunningInAppExtension()) { + failureCallback(@[@"Unable to show action sheet from app extension"]); + return; + } + UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; - UIViewController *ctrl = [UIApplication sharedApplication].delegate.window.rootViewController; + UIViewController *ctrl = RCTSharedApplication().delegate.window.rootViewController; #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 @@ -146,7 +156,7 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options RCTLogWarn(@"No callback registered for action sheet: %@", actionSheet.title); } - [[UIApplication sharedApplication].delegate.window makeKeyWindow]; + [RCTSharedApplication().delegate.window makeKeyWindow]; } #pragma mark Private diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index 00a28a665..281149196 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -10,6 +10,8 @@ #import "RCTImagePickerManager.h" #import "RCTRootView.h" +#import "RCTLog.h" +#import "RCTUtils.h" #import @@ -53,7 +55,12 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config successCallback:(RCTResponseSenderBlock)callback cancelCallback:(RCTResponseSenderBlock)cancelCallback) { - UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + if (RCTRunningInAppExtension()) { + cancelCallback(@[@"Camera access is unavailable in an app extension"]); + return; + } + + UIWindow *keyWindow = RCTSharedApplication().keyWindow; UIViewController *rootViewController = keyWindow.rootViewController; UIImagePickerController *imagePicker = [UIImagePickerController new]; @@ -75,7 +82,12 @@ RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config successCallback:(RCTResponseSenderBlock)callback cancelCallback:(RCTResponseSenderBlock)cancelCallback) { - UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + if (RCTRunningInAppExtension()) { + cancelCallback(@[@"Image picker is currently unavailable in an app extension"]); + return; + } + + UIWindow *keyWindow = RCTSharedApplication().keyWindow; UIViewController *rootViewController = keyWindow.rootViewController; UIImagePickerController *imagePicker = [UIImagePickerController new]; @@ -109,7 +121,7 @@ didFinishPickingMediaWithInfo:(NSDictionary *)info [_pickerCallbacks removeObjectAtIndex:index]; [_pickerCancelCallbacks removeObjectAtIndex:index]; - UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + UIWindow *keyWindow = RCTSharedApplication().keyWindow; UIViewController *rootViewController = keyWindow.rootViewController; [rootViewController dismissViewControllerAnimated:YES completion:nil]; @@ -125,7 +137,7 @@ didFinishPickingMediaWithInfo:(NSDictionary *)info [_pickerCallbacks removeObjectAtIndex:index]; [_pickerCancelCallbacks removeObjectAtIndex:index]; - UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + UIWindow *keyWindow = RCTSharedApplication().keyWindow; UIViewController *rootViewController = keyWindow.rootViewController; [rootViewController dismissViewControllerAnimated:YES completion:nil]; diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index af1d41752..37a2cc4d6 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -58,14 +58,20 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(openURL:(NSURL *)URL) { // Doesn't really matter what thread we call this on since it exits the app - [[UIApplication sharedApplication] openURL:URL]; + [RCTSharedApplication() openURL:URL]; } RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL callback:(RCTResponseSenderBlock)callback) { + if (RCTRunningInAppExtension()) { + // Technically Today widgets can open urls, but supporting that would require + // a reference to the NSExtensionContext + callback(@[@(NO)]); + } + // This can be expensive, so we deliberately don't call on main thread - BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL]; + BOOL canOpen = [RCTSharedApplication() canOpenURL:URL]; callback(@[@(canOpen)]); } diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index fda9980f9..6af5f8e91 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -122,7 +122,7 @@ RCT_EXPORT_MODULE() */ RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(NSInteger)number) { - [UIApplication sharedApplication].applicationIconBadgeNumber = number; + RCTSharedApplication().applicationIconBadgeNumber = number; } /** @@ -131,12 +131,16 @@ RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(NSInteger)number) RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback) { callback(@[ - @([UIApplication sharedApplication].applicationIconBadgeNumber) + @(RCTSharedApplication().applicationIconBadgeNumber) ]); } RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) { + if (RCTRunningInAppExtension()) { + return; + } + UIUserNotificationType types = UIUserNotificationTypeNone; if (permissions) { if ([permissions[@"alert"] boolValue]) { @@ -152,35 +156,37 @@ RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound; } + UIApplication *app = RCTSharedApplication(); #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 - id notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; - [[UIApplication sharedApplication] registerForRemoteNotifications]; - + [app registerUserNotificationSettings:notificationSettings]; + [app registerForRemoteNotifications]; #else - - [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types]; - + [app registerForRemoteNotificationTypes:types]; #endif - } RCT_EXPORT_METHOD(abandonPermissions) { - [[UIApplication sharedApplication] unregisterForRemoteNotifications]; + [RCTSharedApplication() unregisterForRemoteNotifications]; } RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { + if (RCTRunningInAppExtension()) { + NSDictionary *permissions = @{@"alert": @(NO), @"badge": @(NO), @"sound": @(NO)}; + callback(@[permissions]); + return; + } + NSUInteger types = 0; if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) { - types = [[UIApplication sharedApplication] currentUserNotificationSettings].types; + types = [RCTSharedApplication() currentUserNotificationSettings].types; } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; + types = [RCTSharedApplication() enabledRemoteNotificationTypes]; #endif @@ -203,13 +209,13 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification) { - [[UIApplication sharedApplication] presentLocalNotificationNow:notification]; + [RCTSharedApplication() presentLocalNotificationNow:notification]; } RCT_EXPORT_METHOD(scheduleLocalNotification:(UILocalNotification *)notification) { - [[UIApplication sharedApplication] scheduleLocalNotification:notification]; + [RCTSharedApplication() scheduleLocalNotification:notification]; } @end diff --git a/React/Base/RCTPerfStats.m b/React/Base/RCTPerfStats.m index ba841dc67..462aa0f9e 100644 --- a/React/Base/RCTPerfStats.m +++ b/React/Base/RCTPerfStats.m @@ -10,6 +10,7 @@ #import "RCTPerfStats.h" #import "RCTDefines.h" +#import "RCTUtils.h" #if RCT_DEV @@ -66,7 +67,11 @@ RCT_EXPORT_MODULE() - (void)show { - UIView *targetView = [UIApplication sharedApplication].delegate.window.rootViewController.view; + if (RCTRunningInAppExtension()) { + return; + } + + UIView *targetView = RCTSharedApplication().delegate.window.rootViewController.view; targetView.frame = (CGRect){ targetView.frame.origin, diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 65eb99f8c..bc94d62f8 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -11,6 +11,7 @@ #import #import +#import #import "RCTAssert.h" #import "RCTDefines.h" @@ -51,6 +52,16 @@ RCT_EXTERN NSDictionary *RCTJSErrorFromNSError(NSError *error); // Returns YES if React is running in a test environment RCT_EXTERN BOOL RCTRunningInTestEnvironment(void); +// Returns YES if React is running in an iOS App Extension +RCT_EXTERN BOOL RCTRunningInAppExtension(void); + +// Returns the shared UIApplication instance, or nil if running in an App Extension +RCT_EXTERN UIApplication *RCTSharedApplication(void); + +// Return a UIAlertView initialized with the given values +// or nil if running in an app extension +RCT_EXTERN UIAlertView *RCTAlertView(NSString *title, NSString *message, id delegate, NSString *cancelButtonTitle, NSArray *otherButtonTitles); + // Return YES if image has an alpha component RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 2fba55f00..c3e59b06f 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -337,6 +337,42 @@ BOOL RCTRunningInTestEnvironment(void) return isTestEnvironment; } +BOOL RCTRunningInAppExtension(void) +{ + return [[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"]; +} + +id RCTSharedApplication(void) +{ + if (RCTRunningInAppExtension()) { + return nil; + } + + return [[UIApplication class] performSelector:@selector(sharedApplication)]; +} + +id RCTAlertView(NSString *title, NSString *message, id delegate, NSString *cancelButtonTitle, NSArray *otherButtonTitles) +{ + if (RCTRunningInAppExtension()) { + RCTLogError(@"RCTAlertView is unavailable when running in an app extension"); + return nil; + } + + UIAlertView *alertView = [[UIAlertView alloc] init]; + alertView.title = title; + alertView.message = message; + alertView.delegate = delegate; + if (cancelButtonTitle != nil) { + [alertView addButtonWithTitle:cancelButtonTitle]; + alertView.cancelButtonIndex = 0; + } + for (NSString *buttonTitle in otherButtonTitles) + { + [alertView addButtonWithTitle:buttonTitle]; + } + return alertView; +} + BOOL RCTImageHasAlpha(CGImageRef image) { switch (CGImageGetAlphaInfo(image)) { diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index b0475e231..b07dcde54 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -11,6 +11,7 @@ #import "RCTAssert.h" #import "RCTLog.h" +#import "RCTUtils.h" @interface RCTAlertManager() @@ -76,13 +77,12 @@ RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args RCTLogError(@"Must have at least one button."); return; } - - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title - message:nil - delegate:self - cancelButtonTitle:nil - otherButtonTitles:nil]; - + + if (RCTRunningInAppExtension()) { + return; + } + + UIAlertView *alertView = RCTAlertView(title, nil, self, nil, nil); NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count]; if ([type isEqualToString:@"plain-text"]) { diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m index a72ea2cc3..294330643 100644 --- a/React/Modules/RCTAppState.m +++ b/React/Modules/RCTAppState.m @@ -12,6 +12,7 @@ #import "RCTAssert.h" #import "RCTBridge.h" #import "RCTEventDispatcher.h" +#import "RCTUtils.h" static NSString *RCTCurrentAppBackgroundState() { @@ -25,7 +26,11 @@ static NSString *RCTCurrentAppBackgroundState() }; }); - return states[@([UIApplication sharedApplication].applicationState)] ?: @"unknown"; + if (RCTRunningInAppExtension()) { + return @"extension"; + } + + return states[@(RCTSharedApplication().applicationState)] ?: @"unknown"; } @implementation RCTAppState diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index 63ac438d1..98335d7b4 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -407,11 +407,8 @@ RCT_EXPORT_MODULE() Class chromeExecutorClass = NSClassFromString(@"RCTWebSocketExecutor"); if (!chromeExecutorClass) { [items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Chrome Debugger Unavailable" handler:^{ - [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable" - message:@"You need to include the RCTWebSocket library to enable Chrome debugging" - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil] show]; + UIAlertView *alert = RCTAlertView(@"Chrome Debugger Unavailable", @"You need to include the RCTWebSocket library to enable Chrome debugging", nil, @"OK", nil); + [alert show]; }]]; } else { BOOL isDebuggingInChrome = _executorClass && _executorClass == chromeExecutorClass; @@ -447,7 +444,7 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(show) { - if (_actionSheet || !_bridge) { + if (_actionSheet || !_bridge || RCTRunningInAppExtension()) { return; } @@ -474,7 +471,7 @@ RCT_EXPORT_METHOD(show) actionSheet.cancelButtonIndex = actionSheet.numberOfButtons - 1; actionSheet.actionSheetStyle = UIBarStyleBlack; - [actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; + [actionSheet showInView:RCTSharedApplication().keyWindow.rootViewController.view]; _actionSheet = actionSheet; _presentedItems = items; } diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index 4d82278c9..ecf3fd921 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -126,7 +126,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) { self.hidden = YES; [self resignFirstResponder]; - [[UIApplication sharedApplication].delegate.window makeKeyWindow]; + [RCTSharedApplication().delegate.window makeKeyWindow]; } - (void)reload diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index ce2364e2b..893c3e0f3 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -11,6 +11,7 @@ #import "RCTEventDispatcher.h" #import "RCTLog.h" +#import "RCTUtils.h" @implementation RCTConvert (UIStatusBar) @@ -97,8 +98,8 @@ RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle RCTLogError(@"RCTStatusBarManager module requires that the \ UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); } else { - [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle - animated:animated]; + [RCTSharedApplication() setStatusBarStyle:statusBarStyle + animated:animated]; } } @@ -109,14 +110,14 @@ RCT_EXPORT_METHOD(setHidden:(BOOL)hidden RCTLogError(@"RCTStatusBarManager module requires that the \ UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); } else { - [[UIApplication sharedApplication] setStatusBarHidden:hidden - withAnimation:animation]; + [RCTSharedApplication() setStatusBarHidden:hidden + withAnimation:animation]; } } RCT_EXPORT_METHOD(setNetworkActivityIndicatorVisible:(BOOL)visible) { - [UIApplication sharedApplication].networkActivityIndicatorVisible = visible; + RCTSharedApplication().networkActivityIndicatorVisible = visible; } @end