react-native/Libraries/ActionSheetIOS/RCTActionSheetManager.m
Yusef Napora 2f9bd1f62f App Extension support
Summary: This adds workarounds for the code that was preventing React from compiling when linked against an iOS App Extension target.

Some iOS APIs are unavailable to App Extensions, and Xcode's static analysis will catch attempts to use methods that have been flagged as unavailable.

React currently uses two APIs that are off limits to extensions: `[UIApplication sharedApplication]` and `[UIAlertView initWith ...]`.

This commit adds a helper function to `RCTUtils.[hm]` called `RCTRunningInAppExtension()`, which returns `YES` if, at runtime, it can be determined that we're running in an app extension (by checking whether the path to `[NSBundle mainBundle]` has the `"appex"` path extension).

It also adds a `RCTSharedApplication()` function, which will return `nil` if running in an App Extension. If running in an App, `RCTSharedApplication()` calls `sharedApplication` by calling `performSelector:` on the `UIApplication` class.  This passes the static analysis check, and, in my opinion, obeys the "spirit of th
Closes https://github.com/facebook/react-native/pull/1895

Reviewed By: @​svcscm

Differential Revision: D2224128

Pulled By: @nicklockwood
2015-09-22 10:46:26 -07:00

170 lines
5.1 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTActionSheetManager.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTUtils.h"
#import "RCTBridge.h"
#import "RCTUIManager.h"
@interface RCTActionSheetManager () <UIActionSheetDelegate>
@end
@implementation RCTActionSheetManager
{
NSMutableDictionary *_callbacks;
}
RCT_EXPORT_MODULE()
- (instancetype)init
{
if ((self = [super init])) {
_callbacks = [NSMutableDictionary new];
}
return self;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
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"];
for (NSString *option in options[@"options"]) {
[actionSheet addButtonWithTitle:option];
}
if (options[@"destructiveButtonIndex"]) {
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
}
if (options[@"cancelButtonIndex"]) {
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
}
actionSheet.delegate = self;
_callbacks[RCTKeyForInstance(actionSheet)] = successCallback;
UIWindow *appWindow = RCTSharedApplication().delegate.window;
if (appWindow == nil) {
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
return;
}
[actionSheet showInView:appWindow];
}
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
failureCallback:(RCTResponseSenderBlock)failureCallback
successCallback:(RCTResponseSenderBlock)successCallback)
{
NSMutableArray *items = [NSMutableArray array];
NSString *message = [RCTConvert NSString:options[@"message"]];
if (message) {
[items addObject:message];
}
NSURL *URL = [RCTConvert NSURL:options[@"url"]];
if (URL) {
[items addObject:URL];
}
if (items.count == 0) {
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 = RCTSharedApplication().delegate.window.rootViewController;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
if (![UIActivityViewController instancesRespondToSelector:@selector(setCompletionWithItemsHandler:)]) {
// Legacy iOS 7 implementation
share.completionHandler = ^(NSString *activityType, BOOL completed) {
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
};
} else
#endif
{
// iOS 8 version
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
if (activityError) {
failureCallback(@[RCTNullIfNil(activityError.localizedDescription)]);
} else {
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
}
};
}
/*
* The `anchor` option takes a view to set as the anchor for the share
* popup to point to, on iPads running iOS 8. If it is not passed, it
* defaults to centering the share popup on screen without any arrows.
*/
if ([share respondsToSelector:@selector(popoverPresentationController)]) {
share.popoverPresentationController.sourceView = ctrl.view;
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
if (anchorViewTag) {
UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
share.popoverPresentationController.sourceRect = [anchorView convertRect:anchorView.bounds toView:ctrl.view];
} else {
CGRect sourceRect = CGRectMake(ctrl.view.center.x, ctrl.view.center.y, 1, 1);
share.popoverPresentationController.sourceRect = sourceRect;
share.popoverPresentationController.permittedArrowDirections = 0;
}
}
[ctrl presentViewController:share animated:YES completion:nil];
}
#pragma mark UIActionSheetDelegate Methods
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *key = RCTKeyForInstance(actionSheet);
RCTResponseSenderBlock callback = _callbacks[key];
if (callback) {
callback(@[@(buttonIndex)]);
[_callbacks removeObjectForKey:key];
} else {
RCTLogWarn(@"No callback registered for action sheet: %@", actionSheet.title);
}
[RCTSharedApplication().delegate.window makeKeyWindow];
}
#pragma mark Private
static NSString *RCTKeyForInstance(id instance)
{
return [NSString stringWithFormat:@"%p", instance];
}
@end