Skip to content

Commit

Permalink
iOS: Update RCTAlertManager to use new RCTAlertController (#29295)
Browse files Browse the repository at this point in the history
Summary:
This should fix #29082 and #10471

Currently when an alert is being shown while a modal is being dismissed, it causes the alert not to show and In some cases it causes the UI to become unresponsive. I think this was caused by using RCTPresentedViewController to try and display the Alert the currently presented view. The View the Alert was going to be shown on is dismissed and the modal doesn't show. I implemented a new RCTAlertController to show the alert on top of the view, the modal being dismissed should now not interfere with the alert being shown.

## Changelog

[iOS] [Fixed] - Fixed showing Alert while closing a Modal

Pull Request resolved: #29295

Test Plan:
To recreate the bug:

1. npx react-native init Test --version 0.63.0-rc.1
2. Paste the following code into App.js

```javascript
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * format
 * flow strict-local
 */

import React from 'react';
import {
  SafeAreaView,
  StyleSheet,
  View,
  Text,
  StatusBar,
  Modal,
  Alert
} from 'react-native';

const App: () => React$Node = () => {
  const [visible, setVisible] = React.useState(false)

  const onShowModal = () => {
    setVisible(true)
  }

  onCloseBroken = () => {
    setVisible(false)
    Alert.alert('Alert', 'Alert won\'t show')
  }

  onCloseWorking = () => {
    setVisible(false)
    setTimeout(() => Alert.alert('Alert', 'Works fine'), 10)
  }

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.container}>
        <Text onPress={onShowModal}>Show modal</Text>
      </SafeAreaView>
      <Modal animationType="fade" visible={visible} onRequestClose={onCloseWorking} >
        <View style={styles.container}>
          <Text onPress={onCloseBroken}>Close modal immediately</Text>
          <Text onPress={onCloseWorking}>Close modal with delay</Text>
        </View>
      </Modal>
    </>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-around',
  },
})

export default App

```

3. cd Test && npx react-native run-ios
4. Show the modal and click the `Close modal immediately` button

The first button doesn't show the alert, the second does because it gets rendered after the modal view is dismissed. After this commit, the alert always shows on top of every view properly. You can test by pointing the react native package to my branch by modifying the package json file like this

```
    "react-native": "https://github.com/devon94/react-native.git#fix-ios-modal"
```

I was unable to reproduce the case where it causes the UI to be responsive in the test app but was able to reproduce it in our react native app at work. I can provide a video later if needed but the code is too complex to simplify into a test case.

Reviewed By: sammy-SC

Differential Revision: D22783371

Pulled By: PeteTheHeat

fbshipit-source-id: 3e359645c610074ea855ee5686c59bdb9d6b696b
  • Loading branch information
devon94 authored and facebook-github-bot committed Aug 6, 2020
1 parent 07640dc commit f319ff3
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 26 deletions.
2 changes: 1 addition & 1 deletion RNTester/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ SPEC CHECKSUMS:
RCTTypeSafety: 1ade47a69b092cddf1e4ea21e0c5bdc65cc950b4
React: cafb3c2321f7df55ce90dbf29d513799a79e4418
React-callinvoker: 0dada022d38b73e6e15b33e2a96476153f79bbf6
React-Core: 22edbf19e3f430ce23111ead6741579f7a591ff2
React-Core: 6c6f6c40bb1e031de8a0fafce08c010edfef09ab
React-CoreModules: d13d148c851af5780f864be74bc2165140923dc7
React-cxxreact: 4661b3295e62c6eaada084e2f826c70c71ef11ea
React-jsi: fe94132da767bfc4801968c2a12abae43e9a833e
Expand Down
14 changes: 14 additions & 0 deletions React/CoreModules/RCTAlertController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <UIKit/UIKit.h>

@interface RCTAlertController : UIAlertController

- (void)show:(BOOL)animated completion:(void (^)(void))completion;

@end
36 changes: 36 additions & 0 deletions React/CoreModules/RCTAlertController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <React/RCTUtils.h>

#import "RCTAlertController.h"

@interface RCTAlertController ()

@property (nonatomic, strong) UIWindow *alertWindow;

@end

@implementation RCTAlertController

- (UIWindow *)alertWindow
{
if (_alertWindow == nil) {
_alertWindow = [[UIWindow alloc] initWithFrame:RCTSharedApplication().keyWindow.bounds];
_alertWindow.rootViewController = [UIViewController new];
_alertWindow.windowLevel = UIWindowLevelAlert + 1;
}
return _alertWindow;
}

- (void)show:(BOOL)animated completion:(void (^)(void))completion
{
[self.alertWindow makeKeyAndVisible];
[self.alertWindow.rootViewController presentViewController:self animated:animated completion:completion];
}

@end
31 changes: 6 additions & 25 deletions React/CoreModules/RCTAlertManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import <React/RCTUtils.h>

#import "CoreModulesPlugins.h"
#import "RCTAlertController.h"

@implementation RCTConvert (UIAlertViewStyle)

Expand Down Expand Up @@ -99,29 +100,9 @@ - (void)invalidate
}
}

UIViewController *presentingController = RCTPresentedViewController();
if (presentingController == nil) {
RCTLogError(@"Tried to display alert view but there is no application window. args: %@", @{
@"title" : args.title() ?: [NSNull null],
@"message" : args.message() ?: [NSNull null],
@"buttons" : RCTConvertOptionalVecToArray(
args.buttons(),
^id(id<NSObject> element) {
return element;
})
?: [NSNull null],
@"type" : args.type() ?: [NSNull null],
@"defaultValue" : args.defaultValue() ?: [NSNull null],
@"cancelButtonKey" : args.cancelButtonKey() ?: [NSNull null],
@"destructiveButtonKey" : args.destructiveButtonKey() ?: [NSNull null],
@"keyboardType" : args.keyboardType() ?: [NSNull null],
});
return;
}

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:nil
preferredStyle:UIAlertControllerStyleAlert];
RCTAlertController *alertController = [RCTAlertController alertControllerWithTitle:title
message:nil
preferredStyle:UIAlertControllerStyleAlert];
switch (type) {
case RCTAlertViewStylePlainTextInput: {
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
Expand Down Expand Up @@ -170,7 +151,7 @@ - (void)invalidate
} else if ([buttonKey isEqualToString:destructiveButtonKey]) {
buttonStyle = UIAlertActionStyleDestructive;
}
__weak UIAlertController *weakAlertController = alertController;
__weak RCTAlertController *weakAlertController = alertController;
[alertController
addAction:[UIAlertAction
actionWithTitle:buttonTitle
Expand Down Expand Up @@ -202,7 +183,7 @@ - (void)invalidate
[_alertControllers addObject:alertController];

dispatch_async(dispatch_get_main_queue(), ^{
[presentingController presentViewController:alertController animated:YES completion:nil];
[alertController show:YES completion:nil];
});
}

Expand Down

0 comments on commit f319ff3

Please sign in to comment.