From 2692b920806743459a0f8bb5d8b180308e6a0e8b Mon Sep 17 00:00:00 2001 From: Koichi Nagaoka Date: Fri, 13 Nov 2020 23:53:58 -0800 Subject: [PATCH] Fix cannot working Modal's onDismiss Summary: Fixes: https://github.com/facebook/react-native/issues/29455 Modal's onDismiss is not called on iOS. This issue occurred the commit https://github.com/facebook/react-native/commit/bd2b7d6c0366b5f19de56b71cb706a0af4b0be43 and was fixed the commit https://github.com/facebook/react-native/commit/27a3248a3b37410b5ee6dda421ae00fa485b525c. However, the master and stable-0.63 branches do not have this modified commit applied to them. [iOS] [Fixed] - Modal's onDismiss prop will now be called successfully. Test Plan: Tested on iOS with this change: 1. Set function Modal's onDismiss prop. 1. Set Modal's visible props is true. (show Modal) 1. Set Modal's visible props is false. (close Modal) 1. The set function in onDismiss is called. --- Libraries/Modal/Modal.js | 26 +++++++++++- .../Modal/RCTModalHostViewNativeComponent.js | 9 ++++ React/Views/RCTModalHostViewManager.m | 10 ++++- React/Views/RCTModalManager.h | 17 ++++++++ React/Views/RCTModalManager.m | 42 +++++++++++++++++++ 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 React/Views/RCTModalManager.h create mode 100644 React/Views/RCTModalManager.m diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 71293402c36186..1e4d0341d6739a 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -13,6 +13,9 @@ const AppContainer = require('../ReactNative/AppContainer'); const I18nManager = require('../ReactNative/I18nManager'); const PropTypes = require('prop-types'); +import NativeEventEmitter from '../EventEmitter/NativeEventEmitter'; +import NativeModalManager from './NativeModalManager'; +const Platform = require('../Utilities/Platform'); const React = require('react'); const ScrollView = require('../Components/ScrollView/ScrollView'); const StyleSheet = require('../StyleSheet/StyleSheet'); @@ -22,6 +25,12 @@ import type {ViewProps} from '../Components/View/ViewPropTypes'; import type {DirectEventHandler} from '../Types/CodegenTypes'; import type EmitterSubscription from '../vendor/emitter/EmitterSubscription'; import RCTModalHostView from './RCTModalHostViewNativeComponent'; + +const ModalEventEmitter = + Platform.OS === 'ios' && NativeModalManager != null + ? new NativeEventEmitter(NativeModalManager) + : null; + /** * The Modal component is a simple way to present content above an enclosing view. * @@ -173,9 +182,22 @@ class Modal extends React.Component { }; } + componentDidMount() { + if (ModalEventEmitter) { + this._eventSubscription = ModalEventEmitter.addListener( + 'modalDismissed', + event => { + if (event.modalID === this._identifier && this.props.onDismiss) { + this.props.onDismiss(); + } + }, + ); + } + } + componentWillUnmount() { - if (this.props.onDismiss != null) { - this.props.onDismiss(); + if (this._eventSubscription) { + this._eventSubscription.remove(); } } diff --git a/Libraries/Modal/RCTModalHostViewNativeComponent.js b/Libraries/Modal/RCTModalHostViewNativeComponent.js index 62bd8a8daf144a..7d8151eae25df0 100644 --- a/Libraries/Modal/RCTModalHostViewNativeComponent.js +++ b/Libraries/Modal/RCTModalHostViewNativeComponent.js @@ -15,6 +15,7 @@ import type {HostComponent} from '../Renderer/shims/ReactNativeTypes'; import type { WithDefault, DirectEventHandler, + BubblingEventHandler, Int32, } from '../Types/CodegenTypes'; @@ -86,6 +87,14 @@ type NativeProps = $ReadOnly<{| */ onShow?: ?DirectEventHandler, + /** + * The `onDismiss` prop allows passing a function that will be called once + * the modal has been dismissed. + * + * See https://reactnative.dev/docs/modal.html#ondismiss + */ + onDismiss?: ?BubblingEventHandler, + /** * Deprecated. Use the `animationType` prop instead. */ diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index bafab9dff9f3a9..2f6cd41ab5a29e 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -10,6 +10,7 @@ #import "RCTBridge.h" #import "RCTModalHostView.h" #import "RCTModalHostViewController.h" +#import "RCTModalManager.h" #import "RCTShadowView.h" #import "RCTUtils.h" @@ -89,10 +90,15 @@ - (void)dismissModalHostView:(RCTModalHostView *)modalHostView withViewController:(RCTModalHostViewController *)viewController animated:(BOOL)animated { + dispatch_block_t completionBlock = ^{ + if (modalHostView.identifier) { + [[self.bridge moduleForClass:[RCTModalManager class]] modalDismissed:modalHostView.identifier]; + } + }; if (_dismissalBlock) { - _dismissalBlock([modalHostView reactViewController], viewController, animated, nil); + _dismissalBlock([modalHostView reactViewController], viewController, animated, completionBlock); } else { - [viewController.presentingViewController dismissViewControllerAnimated:animated completion:nil]; + [viewController.presentingViewController dismissViewControllerAnimated:animated completion:completionBlock]; } } diff --git a/React/Views/RCTModalManager.h b/React/Views/RCTModalManager.h new file mode 100644 index 00000000000000..4fbe6dfbd01e25 --- /dev/null +++ b/React/Views/RCTModalManager.h @@ -0,0 +1,17 @@ +/* + * 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 + +#import +#import + +@interface RCTModalManager : RCTEventEmitter + +- (void)modalDismissed:(NSNumber *)modalID; + +@end diff --git a/React/Views/RCTModalManager.m b/React/Views/RCTModalManager.m new file mode 100644 index 00000000000000..992b73c62db660 --- /dev/null +++ b/React/Views/RCTModalManager.m @@ -0,0 +1,42 @@ +/* + * 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 "RCTModalManager.h" + +@interface RCTModalManager () + +@property BOOL shouldEmit; + +@end + +@implementation RCTModalManager + +RCT_EXPORT_MODULE(); + +- (NSArray *)supportedEvents +{ + return @[ @"modalDismissed" ]; +} + +- (void)startObserving +{ + _shouldEmit = YES; +} + +- (void)stopObserving +{ + _shouldEmit = NO; +} + +- (void)modalDismissed:(NSNumber *)modalID +{ + if (_shouldEmit) { + [self sendEventWithName:@"modalDismissed" body:@{@"modalID" : modalID}]; + } +} + +@end