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