Skip to content

Commit

Permalink
migrate RCTActionSheetManager to handle synchronous void method execu…
Browse files Browse the repository at this point in the history
…tion

Summary:
Changelog: [Internal]

in the future, all void native module methods will execute synchronously.

currently, many modules override the methodQueue selector to return the main queue so their async methods will be executed on the main thread by our infra. now that void methods are executing synchronously, this override will be ignored, thus causing unpredictable behavior for those methods that do depend on being run on main thread to behave correctly.

the migration in this stack will prevent bugs caused by this behavioral change by explicitly dispatching execution onto the main thread.

Reviewed By: mdvacca

Differential Revision: D50635827

fbshipit-source-id: 384ee2f0237a49dc4f50e4171092c864f2f55327
  • Loading branch information
philIip authored and facebook-github-bot committed Oct 27, 2023
1 parent ac7b158 commit 3f621df
Showing 1 changed file with 138 additions and 138 deletions.
276 changes: 138 additions & 138 deletions packages/react-native/React/CoreModules/RCTActionSheetManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ + (BOOL)requiresMainQueueSetup

@synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED;

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

- (void)presentViewController:(UIViewController *)alertController
onParentViewController:(UIViewController *)parentViewController
anchorViewTag:(NSNumber *)anchorViewTag
Expand Down Expand Up @@ -99,100 +94,102 @@ - (void)presentViewController:(UIViewController *)alertController
NSNumber *destructiveButtonIndex = @-1;
destructiveButtonIndices = @[ destructiveButtonIndex ];
}

UIViewController *controller = RCTPresentedViewController();
NSNumber *anchor = [RCTConvert NSNumber:options.anchor() ? @(*options.anchor()) : nil];
UIColor *tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil];
UIColor *cancelButtonTintColor =
[RCTConvert UIColor:options.cancelButtonTintColor() ? @(*options.cancelButtonTintColor()) : nil];
NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];

if (controller == nil) {
RCTLogError(
@"Tried to display action sheet but there is no application window. options: %@", @{
@"title" : title,
@"message" : message,
@"options" : buttons,
@"cancelButtonIndex" : @(cancelButtonIndex),
@"destructiveButtonIndices" : destructiveButtonIndices,
@"anchor" : anchor,
@"tintColor" : tintColor,
@"cancelButtonTintColor" : cancelButtonTintColor,
@"disabledButtonIndices" : disabledButtonIndices,
});
return;
}
UIViewController *controller = RCTPresentedViewController();

/*
* 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.
*/
NSNumber *anchorViewTag = anchor;

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleActionSheet];

NSInteger index = 0;
bool isCancelButtonIndex = false;
// The handler for a button might get called more than once when tapping outside
// the action sheet on iPad. RCTResponseSenderBlock can only be called once so
// keep track of callback invocation here.
__block bool callbackInvoked = false;
for (NSString *option in buttons) {
UIAlertActionStyle style = UIAlertActionStyleDefault;
if ([destructiveButtonIndices containsObject:@(index)]) {
style = UIAlertActionStyleDestructive;
} else if (index == cancelButtonIndex) {
style = UIAlertActionStyleCancel;
isCancelButtonIndex = true;
dispatch_async(dispatch_get_main_queue(), ^{
if (controller == nil) {
RCTLogError(
@"Tried to display action sheet but there is no application window. options: %@", @{
@"title" : title,
@"message" : message,
@"options" : buttons,
@"cancelButtonIndex" : @(cancelButtonIndex),
@"destructiveButtonIndices" : destructiveButtonIndices,
@"anchor" : anchor,
@"tintColor" : tintColor,
@"cancelButtonTintColor" : cancelButtonTintColor,
@"disabledButtonIndices" : disabledButtonIndices,
});
return;
}

NSInteger localIndex = index;
UIAlertAction *actionButton = [UIAlertAction actionWithTitle:option
style:style
handler:^(__unused UIAlertAction *action) {
if (!callbackInvoked) {
callbackInvoked = true;
[self->_alertControllers removeObject:alertController];
callback(@[ @(localIndex) ]);
}
}];
if (isCancelButtonIndex) {
[actionButton setValue:cancelButtonTintColor forKey:@"titleTextColor"];
}
[alertController addAction:actionButton];
/*
* 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.
*/
NSNumber *anchorViewTag = anchor;

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleActionSheet];

NSInteger index = 0;
bool isCancelButtonIndex = false;
// The handler for a button might get called more than once when tapping outside
// the action sheet on iPad. RCTResponseSenderBlock can only be called once so
// keep track of callback invocation here.
__block bool callbackInvoked = false;
for (NSString *option in buttons) {
UIAlertActionStyle style = UIAlertActionStyleDefault;
if ([destructiveButtonIndices containsObject:@(index)]) {
style = UIAlertActionStyleDestructive;
} else if (index == cancelButtonIndex) {
style = UIAlertActionStyleCancel;
isCancelButtonIndex = true;
}

index++;
}
NSInteger localIndex = index;
UIAlertAction *actionButton = [UIAlertAction actionWithTitle:option
style:style
handler:^(__unused UIAlertAction *action) {
if (!callbackInvoked) {
callbackInvoked = true;
[self->_alertControllers removeObject:alertController];
callback(@[ @(localIndex) ]);
}
}];
if (isCancelButtonIndex) {
[actionButton setValue:cancelButtonTintColor forKey:@"titleTextColor"];
}
[alertController addAction:actionButton];

if (disabledButtonIndices) {
for (NSNumber *disabledButtonIndex in disabledButtonIndices) {
if ([disabledButtonIndex integerValue] < buttons.count) {
[alertController.actions[[disabledButtonIndex integerValue]] setEnabled:false];
} else {
RCTLogError(
@"Index %@ from `disabledButtonIndices` is out of bounds. Maximum index value is %@.",
@([disabledButtonIndex integerValue]),
@(buttons.count - 1));
return;
index++;
}

if (disabledButtonIndices) {
for (NSNumber *disabledButtonIndex in disabledButtonIndices) {
if ([disabledButtonIndex integerValue] < buttons.count) {
[alertController.actions[[disabledButtonIndex integerValue]] setEnabled:false];
} else {
RCTLogError(
@"Index %@ from `disabledButtonIndices` is out of bounds. Maximum index value is %@.",
@([disabledButtonIndex integerValue]),
@(buttons.count - 1));
return;
}
}
}
}

alertController.view.tintColor = tintColor;
NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];
alertController.view.tintColor = tintColor;

if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
alertController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}

[_alertControllers addObject:alertController];
[self presentViewController:alertController onParentViewController:controller anchorViewTag:anchorViewTag];
[self->_alertControllers addObject:alertController];
[self presentViewController:alertController onParentViewController:controller anchorViewTag:anchorViewTag];
});
}

RCT_EXPORT_METHOD(dismissActionSheet)
Expand All @@ -201,9 +198,11 @@ - (void)presentViewController:(UIViewController *)alertController
RCTLogWarn(@"Unable to dismiss action sheet");
}

id _alertController = [_alertControllers lastObject];
[_alertController dismissViewControllerAnimated:YES completion:nil];
[_alertControllers removeLastObject];
UIAlertController *alertController = [_alertControllers lastObject];
dispatch_async(dispatch_get_main_queue(), ^{
[alertController dismissViewControllerAnimated:YES completion:nil];
[self->_alertControllers removeLastObject];
});
}

RCT_EXPORT_METHOD(showShareActionSheetWithOptions
Expand All @@ -218,68 +217,69 @@ - (void)presentViewController:(UIViewController *)alertController

NSMutableArray<id> *items = [NSMutableArray array];
NSString *message = options.message();
if (message) {
[items addObject:message];
}
NSURL *URL = [RCTConvert NSURL:options.url()];
if (URL) {
if ([URL.scheme.lowercaseString isEqualToString:@"data"]) {
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:URL options:(NSDataReadingOptions)0 error:&error];
if (!data) {
failureCallback(@[ RCTJSErrorFromNSError(error) ]);
return;
}
[items addObject:data];
} else {
[items addObject:URL];
}
}
if (items.count == 0) {
RCTLogError(@"No `url` or `message` to share");
return;
}

UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items
applicationActivities:nil];

NSString *subject = options.subject();
if (subject) {
[shareController setValue:subject forKey:@"subject"];
}

NSArray *excludedActivityTypes =
RCTConvertOptionalVecToArray(options.excludedActivityTypes(), ^id(NSString *element) {
return element;
});
if (excludedActivityTypes) {
shareController.excludedActivityTypes = excludedActivityTypes;
}
NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];
NSNumber *anchorViewTag = [RCTConvert NSNumber:options.anchor() ? @(*options.anchor()) : nil];
UIColor *tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil];

UIViewController *controller = RCTPresentedViewController();
shareController.completionWithItemsHandler =
^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
if (activityError) {
failureCallback(@[ RCTJSErrorFromNSError(activityError) ]);
} else if (completed || activityType == nil) {
successCallback(@[ @(completed), RCTNullIfNil(activityType) ]);
dispatch_async(dispatch_get_main_queue(), ^{
if (message) {
[items addObject:message];
}
if (URL) {
if ([URL.scheme.lowercaseString isEqualToString:@"data"]) {
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:URL options:(NSDataReadingOptions)0 error:&error];
if (!data) {
failureCallback(@[ RCTJSErrorFromNSError(error) ]);
return;
}
};

NSNumber *anchorViewTag = [RCTConvert NSNumber:options.anchor() ? @(*options.anchor()) : nil];
shareController.view.tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil];
[items addObject:data];
} else {
[items addObject:URL];
}
}
if (items.count == 0) {
RCTLogError(@"No `url` or `message` to share");
return;
}

NSString *userInterfaceStyle = [RCTConvert NSString:options.userInterfaceStyle()];
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items
applicationActivities:nil];
if (subject) {
[shareController setValue:subject forKey:@"subject"];
}
if (excludedActivityTypes) {
shareController.excludedActivityTypes = excludedActivityTypes;
}

if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
UIViewController *controller = RCTPresentedViewController();
shareController.completionWithItemsHandler =
^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
if (activityError) {
failureCallback(@[ RCTJSErrorFromNSError(activityError) ]);
} else if (completed || activityType == nil) {
successCallback(@[ @(completed), RCTNullIfNil(activityType) ]);
}
};

shareController.view.tintColor = tintColor;

if (userInterfaceStyle == nil || [userInterfaceStyle isEqualToString:@""]) {
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
} else if ([userInterfaceStyle isEqualToString:@"dark"]) {
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
} else if ([userInterfaceStyle isEqualToString:@"light"]) {
shareController.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}

[self presentViewController:shareController onParentViewController:controller anchorViewTag:anchorViewTag];
[self presentViewController:shareController onParentViewController:controller anchorViewTag:anchorViewTag];
});
}

- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params
Expand Down

0 comments on commit 3f621df

Please sign in to comment.