diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 7cb0ddf51..7edeb39bd 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -13,6 +13,8 @@ const AppContainer = require('AppContainer'); const I18nManager = require('I18nManager'); +const NativeEventEmitter = require('NativeEventEmitter'); +const NativeModules = require('NativeModules'); const Platform = require('Platform'); const React = require('React'); const PropTypes = require('prop-types'); @@ -22,6 +24,10 @@ const View = require('View'); const deprecatedPropType = require('deprecatedPropType'); const requireNativeComponent = require('requireNativeComponent'); const RCTModalHostView = requireNativeComponent('RCTModalHostView', null); +const ModalEventEmitter = Platform.OS === 'ios' && NativeModules.ModalManager ? + new NativeEventEmitter(NativeModules.ModalManager) : null; + +import type EmitterSubscription from 'EmitterSubscription'; /** * The Modal component is a simple way to present content above an enclosing view. @@ -79,6 +85,12 @@ const RCTModalHostView = requireNativeComponent('RCTModalHostView', null); * ``` */ +// In order to route onDismiss callbacks, we need to uniquely identifier each +// on screen. There can be different ones, either nested or as siblings. +// We cannot pass the onDismiss callback to native as the view will be +// destroyed before the callback is fired. +var uniqueModalIdentifier = 0; + class Modal extends React.Component { static propTypes = { /** @@ -125,6 +137,11 @@ class Modal extends React.Component { * The `onShow` prop allows passing a function that will be called once the modal has been shown. */ onShow: PropTypes.func, + /** + * The `onDismiss` prop allows passing a function that will be called once the modal has been dismissed. + * @platform ios + */ + onDismiss: PropTypes.func, animated: deprecatedPropType( PropTypes.bool, 'Use the `animationType` prop instead.' @@ -153,9 +170,32 @@ class Modal extends React.Component { rootTag: PropTypes.number, }; + _identifier: number; + _eventSubscription: ?EmitterSubscription; + constructor(props: Object) { super(props); Modal._confirmProps(props); + this._identifier = uniqueModalIdentifier++; + } + + componentDidMount() { + if (ModalEventEmitter) { + this._eventSubscription = ModalEventEmitter.addListener( + 'modalDismissed', + event => { + if (event.modalID === this._identifier && this.props.onDismiss) { + this.props.onDismiss(); + } + }, + ); + } + } + + componentWillUnmount() { + if (this._eventSubscription) { + this._eventSubscription.remove(); + } } componentWillReceiveProps(nextProps: Object) { @@ -208,6 +248,7 @@ class Modal extends React.Component { hardwareAccelerated={this.props.hardwareAccelerated} onRequestClose={this.props.onRequestClose} onShow={this.props.onShow} + identifier={this._identifier} style={styles.modal} onStartShouldSetResponder={this._shouldSetResponder} supportedOrientations={this.props.supportedOrientations} diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 6213ab1f4..b6677d57f 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -1071,6 +1071,7 @@ 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; }; 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; }; 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; }; + 916F9C2D1F743F57002E5920 /* RCTModalManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 91076A871F743AB00081B4FA /* RCTModalManager.m */; }; 9936F3371F5F2F480010BF04 /* PrivateDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9936F3351F5F2F480010BF04 /* PrivateDataBase.cpp */; }; 9936F3381F5F2F480010BF04 /* PrivateDataBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9936F3361F5F2F480010BF04 /* PrivateDataBase.h */; }; 9936F3391F5F2F5C0010BF04 /* PrivateDataBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9936F3361F5F2F480010BF04 /* PrivateDataBase.h */; }; @@ -2084,6 +2085,8 @@ 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTConvert.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; }; 83F15A171B7CC46900F10295 /* UIView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Private.h"; sourceTree = ""; }; + 91076A871F743AB00081B4FA /* RCTModalManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTModalManager.m; sourceTree = ""; }; + 91076A881F743AB00081B4FA /* RCTModalManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTModalManager.h; sourceTree = ""; }; 9936F3131F5F2E4B0010BF04 /* libprivatedata.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libprivatedata.a; sourceTree = BUILT_PRODUCTS_DIR; }; 9936F32F1F5F2E5B0010BF04 /* libprivatedata-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libprivatedata-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 9936F3351F5F2F480010BF04 /* PrivateDataBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PrivateDataBase.cpp; path = privatedata/PrivateDataBase.cpp; sourceTree = ""; }; @@ -2373,6 +2376,8 @@ 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */, 83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */, 83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */, + 91076A881F743AB00081B4FA /* RCTModalManager.h */, + 91076A871F743AB00081B4FA /* RCTModalManager.m */, 13B0800C1A69489C00A75B9A /* RCTNavigator.h */, 13B0800D1A69489C00A75B9A /* RCTNavigator.m */, 13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */, @@ -4144,6 +4149,7 @@ 1450FF861BCFF28A00208362 /* RCTProfile.m in Sources */, 13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */, 13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */, + 916F9C2D1F743F57002E5920 /* RCTModalManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/React/ReactLegacy.xcodeproj/project.pbxproj b/React/ReactLegacy.xcodeproj/project.pbxproj index 280772042..ba6fcfaa6 100644 --- a/React/ReactLegacy.xcodeproj/project.pbxproj +++ b/React/ReactLegacy.xcodeproj/project.pbxproj @@ -725,6 +725,7 @@ 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; }; 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; }; 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; }; + 916F9C2E1F743F7E002E5920 /* RCTModalManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 91076A891F743AD70081B4FA /* RCTModalManager.m */; }; 945929C41DD62ADD00653A7D /* RCTConvert+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */; }; 945929C51DD62ADD00653A7D /* RCTConvert+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */; }; A12E9E1B1E5DEA350029001B /* RCTPackagerClient.h in Headers */ = {isa = PBXBuildFile; fileRef = A12E9E171E5DEA350029001B /* RCTPackagerClient.h */; }; @@ -1412,6 +1413,8 @@ 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTConvert.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; }; 83F15A171B7CC46900F10295 /* UIView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Private.h"; sourceTree = ""; }; + 91076A891F743AD70081B4FA /* RCTModalManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTModalManager.m; sourceTree = ""; }; + 91076A8A1F743AD70081B4FA /* RCTModalManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTModalManager.h; sourceTree = ""; }; 945929C21DD62ADD00653A7D /* RCTConvert+Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Transform.h"; sourceTree = ""; }; 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Transform.m"; sourceTree = ""; }; A12E9E171E5DEA350029001B /* RCTPackagerClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPackagerClient.h; sourceTree = ""; }; @@ -1587,6 +1590,8 @@ 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */, 83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */, 83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */, + 91076A8A1F743AD70081B4FA /* RCTModalManager.h */, + 91076A891F743AD70081B4FA /* RCTModalManager.m */, 13B0800C1A69489C00A75B9A /* RCTNavigator.h */, 13B0800D1A69489C00A75B9A /* RCTNavigator.m */, 13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */, @@ -2700,6 +2705,7 @@ 597AD1BF1E577D7800152581 /* RCTRootContentView.m in Sources */, 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */, 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */, + 916F9C2E1F743F7E002E5920 /* RCTModalManager.m in Sources */, 001BFCD01D8381DE008E587E /* RCTMultipartStreamReader.m in Sources */, 133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */, 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */, diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h index e0c9fcb9e..40b5a35e2 100644 --- a/React/Views/RCTModalHostView.h +++ b/React/Views/RCTModalHostView.h @@ -26,6 +26,8 @@ @property (nonatomic, copy) RCTDirectEventBlock onShow; +@property (nonatomic, copy) NSNumber *identifier; + @property (nonatomic, weak) id delegate; @property (nonatomic, copy) NSArray *supportedOrientations; diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index fc4cc59ae..dbc1f6af8 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -12,6 +12,7 @@ #import "RCTBridge.h" #import "RCTModalHostView.h" #import "RCTModalHostViewController.h" +#import "RCTModalManager.h" #import "RCTShadowView.h" #import "RCTUtils.h" @@ -82,10 +83,15 @@ RCT_EXPORT_MODULE() - (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 dismissViewControllerAnimated:animated completion:nil]; + [viewController dismissViewControllerAnimated:animated completion:completionBlock]; } } @@ -107,6 +113,7 @@ RCT_EXPORT_VIEW_PROPERTY(animationType, NSString) RCT_EXPORT_VIEW_PROPERTY(presentationStyle, UIModalPresentationStyle) RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL) RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(identifier, NSNumber) RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray) RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock) diff --git a/React/Views/RCTModalManager.h b/React/Views/RCTModalManager.h new file mode 100644 index 000000000..33c0784e9 --- /dev/null +++ b/React/Views/RCTModalManager.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#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 000000000..d49d51816 --- /dev/null +++ b/React/Views/RCTModalManager.m @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTModalManager.h" + +@implementation RCTModalManager + +RCT_EXPORT_MODULE(); + +- (NSArray *)supportedEvents +{ + return @[ @"modalDismissed" ]; +} + +- (void)modalDismissed:(NSNumber *)modalID +{ + [self sendEventWithName:@"modalDismissed" body:@{ @"modalID": modalID }]; +} + +@end