fix background access to UIKit in RCTAlertManager (#42684)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/42684

This addressed threading issue like this:

```
Attempting to set an overrideUserInterfaceStyle from a background thread. Modifying a view controller from a background thread is not supported
```

Changelog: [iOS][Fixed] Fixed potential threading issues accessing UIKit from background in RCTAlertManager

Reviewed By: philIip

Differential Revision: D52999194

fbshipit-source-id: 8ce8a89ef932ca9b75cb93d3c9f102a6b0494580
This commit is contained in:
Kevin Gozali
2024-01-26 14:08:12 -08:00
committed by Facebook GitHub Bot
parent 751eae94d4
commit 8c4979e7a1
@@ -49,9 +49,11 @@ RCT_EXPORT_MODULE()
- (void)invalidate
{
for (UIAlertController *alertController in _alertControllers) {
[alertController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
RCTExecuteOnMainQueue(^{
for (UIAlertController *alertController in self->_alertControllers) {
[alertController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
});
}
/**
@@ -101,101 +103,100 @@ RCT_EXPORT_METHOD(alertWithArgs : (JS::NativeAlertManager::Args &)args callback
}
}
RCTAlertController *alertController = [RCTAlertController alertControllerWithTitle:title
message:nil
preferredStyle:UIAlertControllerStyleAlert];
RCTExecuteOnMainQueue(^{
RCTAlertController *alertController = [RCTAlertController alertControllerWithTitle:title
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIUserInterfaceStyle userInterfaceStyle = [RCTConvert UIUserInterfaceStyle:args.userInterfaceStyle()];
alertController.overrideUserInterfaceStyle = userInterfaceStyle;
UIUserInterfaceStyle userInterfaceStyle = [RCTConvert UIUserInterfaceStyle:args.userInterfaceStyle()];
alertController.overrideUserInterfaceStyle = userInterfaceStyle;
switch (type) {
case RCTAlertViewStylePlainTextInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = NO;
textField.text = defaultValue;
textField.keyboardType = keyboardType;
}];
break;
switch (type) {
case RCTAlertViewStylePlainTextInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = NO;
textField.text = defaultValue;
textField.keyboardType = keyboardType;
}];
break;
}
case RCTAlertViewStyleSecureTextInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = RCTUIKitLocalizedString(@"Password");
textField.secureTextEntry = YES;
textField.text = defaultValue;
textField.keyboardType = keyboardType;
}];
break;
}
case RCTAlertViewStyleLoginAndPasswordInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = RCTUIKitLocalizedString(@"Login");
textField.text = defaultValue;
textField.keyboardType = keyboardType;
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = RCTUIKitLocalizedString(@"Password");
textField.secureTextEntry = YES;
}];
break;
}
case RCTAlertViewStyleDefault:
break;
}
case RCTAlertViewStyleSecureTextInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = RCTUIKitLocalizedString(@"Password");
textField.secureTextEntry = YES;
textField.text = defaultValue;
textField.keyboardType = keyboardType;
}];
break;
}
case RCTAlertViewStyleLoginAndPasswordInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = RCTUIKitLocalizedString(@"Login");
textField.text = defaultValue;
textField.keyboardType = keyboardType;
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = RCTUIKitLocalizedString(@"Password");
textField.secureTextEntry = YES;
}];
break;
}
case RCTAlertViewStyleDefault:
break;
}
alertController.message = message;
alertController.message = message;
for (NSDictionary<NSString *, id> *button in buttons) {
if (button.count != 1) {
RCTLogError(@"Button definitions should have exactly one key.");
}
NSString *buttonKey = button.allKeys.firstObject;
NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]];
UIAlertActionStyle buttonStyle = UIAlertActionStyleDefault;
if ([buttonKey isEqualToString:cancelButtonKey]) {
buttonStyle = UIAlertActionStyleCancel;
} else if ([buttonKey isEqualToString:destructiveButtonKey]) {
buttonStyle = UIAlertActionStyleDestructive;
}
__weak RCTAlertController *weakAlertController = alertController;
for (NSDictionary<NSString *, id> *button in buttons) {
if (button.count != 1) {
RCTLogError(@"Button definitions should have exactly one key.");
}
NSString *buttonKey = button.allKeys.firstObject;
NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]];
UIAlertActionStyle buttonStyle = UIAlertActionStyleDefault;
if ([buttonKey isEqualToString:cancelButtonKey]) {
buttonStyle = UIAlertActionStyleCancel;
} else if ([buttonKey isEqualToString:destructiveButtonKey]) {
buttonStyle = UIAlertActionStyleDestructive;
}
__weak RCTAlertController *weakAlertController = alertController;
UIAlertAction *alertAction =
[UIAlertAction actionWithTitle:buttonTitle
style:buttonStyle
handler:^(__unused UIAlertAction *action) {
switch (type) {
case RCTAlertViewStylePlainTextInput:
case RCTAlertViewStyleSecureTextInput:
callback(@[ buttonKey, [weakAlertController.textFields.firstObject text] ]);
[weakAlertController hide];
break;
case RCTAlertViewStyleLoginAndPasswordInput: {
NSDictionary<NSString *, NSString *> *loginCredentials = @{
@"login" : [weakAlertController.textFields.firstObject text],
@"password" : [weakAlertController.textFields.lastObject text]
};
callback(@[ buttonKey, loginCredentials ]);
[weakAlertController hide];
break;
UIAlertAction *alertAction =
[UIAlertAction actionWithTitle:buttonTitle
style:buttonStyle
handler:^(__unused UIAlertAction *action) {
switch (type) {
case RCTAlertViewStylePlainTextInput:
case RCTAlertViewStyleSecureTextInput:
callback(@[ buttonKey, [weakAlertController.textFields.firstObject text] ]);
[weakAlertController hide];
break;
case RCTAlertViewStyleLoginAndPasswordInput: {
NSDictionary<NSString *, NSString *> *loginCredentials = @{
@"login" : [weakAlertController.textFields.firstObject text],
@"password" : [weakAlertController.textFields.lastObject text]
};
callback(@[ buttonKey, loginCredentials ]);
[weakAlertController hide];
break;
}
case RCTAlertViewStyleDefault:
callback(@[ buttonKey ]);
[weakAlertController hide];
break;
}
case RCTAlertViewStyleDefault:
callback(@[ buttonKey ]);
[weakAlertController hide];
break;
}
}];
[alertController addAction:alertAction];
}];
[alertController addAction:alertAction];
if ([buttonKey isEqualToString:preferredButtonKey]) {
[alertController setPreferredAction:alertAction];
if ([buttonKey isEqualToString:preferredButtonKey]) {
[alertController setPreferredAction:alertAction];
}
}
}
if (!_alertControllers) {
_alertControllers = [NSHashTable weakObjectsHashTable];
}
[_alertControllers addObject:alertController];
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_alertControllers) {
self->_alertControllers = [NSHashTable weakObjectsHashTable];
}
[self->_alertControllers addObject:alertController];
[alertController show:YES completion:nil];
});
}