Skip to content

Commit

Permalink
fix background access to UIKit in RCTAlertManager (#42684)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
fkgozali authored and facebook-github-bot committed Jan 26, 2024
1 parent 751eae9 commit 8c4979e
Showing 1 changed file with 94 additions and 93 deletions.
187 changes: 94 additions & 93 deletions packages/react-native/React/CoreModules/RCTAlertManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ - (dispatch_queue_t)methodQueue

- (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];
}
});
}

/**
Expand Down Expand Up @@ -101,101 +103,100 @@ - (void)invalidate
}
}

RCTAlertController *alertController = [RCTAlertController alertControllerWithTitle:title
message:nil
preferredStyle:UIAlertControllerStyleAlert];

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;
}
case RCTAlertViewStyleSecureTextInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = RCTUIKitLocalizedString(@"Password");
textField.secureTextEntry = YES;
textField.text = defaultValue;
textField.keyboardType = keyboardType;
}];
break;
RCTExecuteOnMainQueue(^{
RCTAlertController *alertController = [RCTAlertController alertControllerWithTitle:title
message:nil
preferredStyle:UIAlertControllerStyleAlert];

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;
}
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 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;

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;
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;

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];

if ([buttonKey isEqualToString:preferredButtonKey]) {
[alertController setPreferredAction:alertAction];
}
}
}];
[alertController addAction:alertAction];

if (!_alertControllers) {
_alertControllers = [NSHashTable weakObjectsHashTable];
}
[_alertControllers addObject:alertController];
if ([buttonKey isEqualToString:preferredButtonKey]) {
[alertController setPreferredAction:alertAction];
}
}

dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_alertControllers) {
self->_alertControllers = [NSHashTable weakObjectsHashTable];
}
[self->_alertControllers addObject:alertController];
[alertController show:YES completion:nil];
});
}
Expand Down

0 comments on commit 8c4979e

Please sign in to comment.