From 33717726b9a2c84a360ca5fdd5cbd06f5fced027 Mon Sep 17 00:00:00 2001 From: radex Date: Fri, 14 Feb 2020 07:46:27 -0800 Subject: [PATCH] Moving towards UIWindowScene support (#28058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/28058 I'm taking the first step towards supporting iOS 13 UIScene APIs and modernizing React Native not to assume an app only has a single window. See discussion here: https://github.com/facebook/react-native/issues/25181#issuecomment-505612941 The approach I'm taking is to take advantage of `RootTagContext` and passing it to NativeModules so that they can identify correctly which window they refer to. Here I'm just laying groundwork. - [x] `Alert` and `ActionSheetIOS` take an optional `rootTag` argument that will cause them to appear on the correct window - [x] `StatusBar` methods also have `rootTag` argument added, but it's not fully hooked up on the native side — this turns out to require some more work, see: https://github.com/facebook/react-native/issues/25181#issuecomment-506690818 - [x] `setNetworkActivityIndicatorVisible` is deprecated in iOS 13 - [x] `RCTPerfMonitor`, `RCTProfile` no longer assume `UIApplicationDelegate` has a `window` property (no longer the best practice) — they now just render on the key window Next steps: Add VC-based status bar management (if I get the OK on https://github.com/facebook/react-native/issues/25181#issuecomment-506690818 ), add multiple window demo to RNTester, deprecate Dimensions in favor of a layout context, consider adding hook-based APIs for native modules such as Alert that automatically know which rootTag to pass ## Changelog [Internal] [Changed] - Modernize Modal to use RootTagContext [iOS] [Changed] - `Alert`, `ActionSheetIOS`, `StatusBar` methods now take an optional `surface` argument (for future iPadOS 13 support) [Internal] [Changed] - Do not assume `UIApplicationDelegate` has a `window` property Pull Request resolved: https://github.com/facebook/react-native/pull/25425 Test Plan: - Open RNTester and: - go to Modal and check if it still works - Alert → see if works - ACtionSheetIOS → see if it works - StatusBar → see if it works - Share → see if it works Reviewed By: PeteTheHeat Differential Revision: D16957751 Pulled By: hramos fbshipit-source-id: 7bbe694c17a490a7ba7b03e5ed31e679e2777b68 --- Libraries/ActionSheetIOS/ActionSheetIOS.js | 35 ++++++++++-- .../NativeActionSheetManager.js | 2 + Libraries/Alert/Alert.js | 22 +++++--- Libraries/Alert/NativeAlertManager.js | 1 + .../StatusBar/NativeStatusBarManagerIOS.js | 12 ++++- Libraries/Components/StatusBar/StatusBar.js | 53 ++++++++++++++----- .../FBReactNativeSpec-generated.mm | 8 +-- .../FBReactNativeSpec/FBReactNativeSpec.h | 24 ++++++++- Libraries/Modal/Modal.js | 13 +++-- .../ActionSheetIOS/ActionSheetIOSExample.js | 6 +-- RNTester/js/examples/Alert/AlertExample.js | 17 +++++- .../js/examples/StatusBar/StatusBarExample.js | 26 ++++++++- React/Base/RCTUtils.h | 2 +- React/Base/RCTUtils.m | 5 +- React/CoreModules/RCTActionSheetManager.mm | 10 +++- React/CoreModules/RCTAlertManager.mm | 36 +++++++------ React/CoreModules/RCTDevMenu.mm | 10 ++-- React/CoreModules/RCTPerfMonitor.mm | 4 +- React/CoreModules/RCTRedBox.mm | 2 + React/CoreModules/RCTStatusBarManager.mm | 31 ++++++++--- React/Profiler/RCTProfile.m | 8 +-- 21 files changed, 242 insertions(+), 85 deletions(-) diff --git a/Libraries/ActionSheetIOS/ActionSheetIOS.js b/Libraries/ActionSheetIOS/ActionSheetIOS.js index 1bd16980344472..7c8d9ae83d5b5a 100644 --- a/Libraries/ActionSheetIOS/ActionSheetIOS.js +++ b/Libraries/ActionSheetIOS/ActionSheetIOS.js @@ -11,6 +11,7 @@ 'use strict'; import RCTActionSheetManager from './NativeActionSheetManager'; +import ReactNative from '../Renderer/shims/ReactNative'; const invariant = require('invariant'); const processColor = require('../StyleSheet/processColor'); @@ -41,11 +42,12 @@ const ActionSheetIOS = { options: {| +title?: ?string, +message?: ?string, - +options: Array, + +options: ?Array, +destructiveButtonIndex?: ?number | ?Array, +cancelButtonIndex?: ?number, +anchor?: ?number, +tintColor?: number | string, + +surface?: mixed, |}, callback: (buttonIndex: number) => void, ) { @@ -56,7 +58,13 @@ const ActionSheetIOS = { invariant(typeof callback === 'function', 'Must provide a valid callback'); invariant(RCTActionSheetManager, "ActionSheetManager does't exist"); - const {tintColor, destructiveButtonIndex, ...remainingOptions} = options; + const { + tintColor, + destructiveButtonIndex, + surface, + ...remainingOptions + } = options; + const reactTag = ReactNative.findNodeHandle(surface) ?? -1; let destructiveButtonIndices = null; if (Array.isArray(destructiveButtonIndex)) { @@ -68,6 +76,7 @@ const ActionSheetIOS = { RCTActionSheetManager.showActionSheetWithOptions( { ...remainingOptions, + reactTag, tintColor: processColor(tintColor), destructiveButtonIndices, }, @@ -99,9 +108,17 @@ const ActionSheetIOS = { * See http://facebook.github.io/react-native/docs/actionsheetios.html#showshareactionsheetwithoptions */ showShareActionSheetWithOptions( - options: Object, + options: {| + +message?: ?string, + +url?: ?string, + +subject?: ?string, + +anchor?: ?number, + +tintColor?: ?number | string, + +excludedActivityTypes?: ?Array, + +surface?: mixed, + |}, failureCallback: Function, - successCallback: Function, + successCallback: (completed: boolean, activityType: ?string) => void, ) { invariant( typeof options === 'object' && options !== null, @@ -116,8 +133,16 @@ const ActionSheetIOS = { 'Must provide a valid successCallback', ); invariant(RCTActionSheetManager, "ActionSheetManager does't exist"); + + const {tintColor, surface, ...remainingOptions} = options; + const reactTag = ReactNative.findNodeHandle(surface) ?? -1; + RCTActionSheetManager.showShareActionSheetWithOptions( - {...options, tintColor: processColor(options.tintColor)}, + { + ...remainingOptions, + reactTag, + tintColor: processColor(options.tintColor), + }, failureCallback, successCallback, ); diff --git a/Libraries/ActionSheetIOS/NativeActionSheetManager.js b/Libraries/ActionSheetIOS/NativeActionSheetManager.js index afc2b82f97af55..0348a57c2a5f96 100644 --- a/Libraries/ActionSheetIOS/NativeActionSheetManager.js +++ b/Libraries/ActionSheetIOS/NativeActionSheetManager.js @@ -24,6 +24,7 @@ export interface Spec extends TurboModule { +cancelButtonIndex?: ?number, +anchor?: ?number, +tintColor?: ?number, + +reactTag?: number, |}, callback: (buttonIndex: number) => void, ) => void; @@ -35,6 +36,7 @@ export interface Spec extends TurboModule { +anchor?: ?number, +tintColor?: ?number, +excludedActivityTypes?: ?Array, + +reactTag?: number, |}, failureCallback: (error: {| +domain: string, diff --git a/Libraries/Alert/Alert.js b/Libraries/Alert/Alert.js index 5012bd18f11497..da2f23803e7036 100644 --- a/Libraries/Alert/Alert.js +++ b/Libraries/Alert/Alert.js @@ -15,6 +15,7 @@ import NativeDialogManagerAndroid, { type DialogOptions, } from '../NativeModules/specs/NativeDialogManagerAndroid'; import RCTAlertManager from './RCTAlertManager'; +import ReactNative from '../Renderer/shims/ReactNative'; export type AlertType = | 'default' @@ -30,11 +31,16 @@ export type Buttons = Array<{ }>; type Options = { - cancelable?: ?boolean, - onDismiss?: ?() => void, + +cancelable?: ?boolean, + +onDismiss?: ?() => void, + +surface?: mixed, ... }; +type PromptOptions = { + surface?: mixed, +}; + /** * Launches an alert dialog with the specified title and message. * @@ -45,10 +51,12 @@ class Alert { title: ?string, message?: ?string, buttons?: Buttons, - options?: Options, + options?: Options = {}, ): void { if (Platform.OS === 'ios') { - Alert.prompt(title, message, buttons, 'default'); + Alert.prompt(title, message, buttons, 'default', undefined, undefined, { + surface: options.surface, + }); } else if (Platform.OS === 'android') { if (!NativeDialogManagerAndroid) { return; @@ -61,7 +69,7 @@ class Alert { cancelable: false, }; - if (options && options.cancelable) { + if (options.cancelable) { config.cancelable = options.cancelable; } // At most three buttons (neutral, negative, positive). Ignore rest. @@ -94,7 +102,7 @@ class Alert { buttonPositive.onPress && buttonPositive.onPress(); } } else if (action === constants.dismissed) { - options && options.onDismiss && options.onDismiss(); + options.onDismiss && options.onDismiss(); } }; const onError = errorMessage => console.warn(errorMessage); @@ -109,6 +117,7 @@ class Alert { type?: ?AlertType = 'plain-text', defaultValue?: string, keyboardType?: string, + options?: PromptOptions = {surface: undefined}, ): void { if (Platform.OS === 'ios') { let callbacks = []; @@ -143,6 +152,7 @@ class Alert { cancelButtonKey, destructiveButtonKey, keyboardType, + reactTag: ReactNative.findNodeHandle(options.surface) ?? -1, }, (id, value) => { const cb = callbacks[id]; diff --git a/Libraries/Alert/NativeAlertManager.js b/Libraries/Alert/NativeAlertManager.js index 452be85c5b112f..f181fc36ceb23e 100644 --- a/Libraries/Alert/NativeAlertManager.js +++ b/Libraries/Alert/NativeAlertManager.js @@ -22,6 +22,7 @@ export type Args = {| cancelButtonKey?: string, destructiveButtonKey?: string, keyboardType?: string, + reactTag?: number, |}; export interface Spec extends TurboModule { diff --git a/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.js b/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.js index ecb6d0329ca8c5..95298058f89df2 100644 --- a/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.js +++ b/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.js @@ -31,11 +31,19 @@ export interface Spec extends TurboModule { * - 'dark-content' * - 'light-content' */ - +setStyle: (statusBarStyle?: ?string, animated: boolean) => void; + +setStyle: ( + statusBarStyle?: ?string, + animated: boolean, + reactTag?: number, + ) => void; /** * - withAnimation can be: 'none' | 'fade' | 'slide' */ - +setHidden: (hidden: boolean, withAnimation: string) => void; + +setHidden: ( + hidden: boolean, + withAnimation: string, + reactTag?: number, + ) => void; } export default (TurboModuleRegistry.getEnforcing( diff --git a/Libraries/Components/StatusBar/StatusBar.js b/Libraries/Components/StatusBar/StatusBar.js index b62c0dcb11a432..0b7bd271864530 100644 --- a/Libraries/Components/StatusBar/StatusBar.js +++ b/Libraries/Components/StatusBar/StatusBar.js @@ -11,6 +11,8 @@ 'use strict'; const Platform = require('../../Utilities/Platform'); +const RootTagContext = require('../../ReactNative/RootTagContext'); +import ReactNative from '../../Renderer/shims/ReactNative'; const React = require('react'); const processColor = require('../../StyleSheet/processColor'); @@ -262,11 +264,19 @@ class StatusBar extends React.Component { * @param animation Optional animation when * changing the status bar hidden property. */ - static setHidden(hidden: boolean, animation?: StatusBarAnimation) { + static setHidden( + hidden: boolean, + animation?: StatusBarAnimation, + surface?: mixed, + ) { animation = animation || 'none'; StatusBar._defaultProps.hidden.value = hidden; if (Platform.OS === 'ios') { - NativeStatusBarManagerIOS.setHidden(hidden, animation); + NativeStatusBarManagerIOS.setHidden( + hidden, + animation, + ReactNative.findNodeHandle(surface) ?? -1, + ); } else if (Platform.OS === 'android') { NativeStatusBarManagerAndroid.setHidden(hidden); } @@ -277,11 +287,19 @@ class StatusBar extends React.Component { * @param style Status bar style to set * @param animated Animate the style change. */ - static setBarStyle(style: StatusBarStyle, animated?: boolean) { + static setBarStyle( + style: StatusBarStyle, + animated?: boolean, + surface?: mixed, + ) { animated = animated || false; StatusBar._defaultProps.barStyle.value = style; if (Platform.OS === 'ios') { - NativeStatusBarManagerIOS.setStyle(style, animated); + NativeStatusBarManagerIOS.setStyle( + style, + animated, + ReactNative.findNodeHandle(surface) ?? -1, + ); } else if (Platform.OS === 'android') { NativeStatusBarManagerAndroid.setStyle(style); } @@ -290,6 +308,7 @@ class StatusBar extends React.Component { /** * Control the visibility of the network activity indicator * @param visible Show the indicator. + * @platform ios */ static setNetworkActivityIndicatorVisible(visible: boolean) { if (Platform.OS !== 'ios') { @@ -306,6 +325,7 @@ class StatusBar extends React.Component { * Set the background color for the status bar * @param color Background color. * @param animated Animate the style change. + * @platform android */ static setBackgroundColor(color: string, animated?: boolean) { if (Platform.OS !== 'android') { @@ -329,6 +349,7 @@ class StatusBar extends React.Component { /** * Control the translucency of the status bar * @param translucent Set as translucent. + * @platform android */ static setTranslucent(translucent: boolean) { if (Platform.OS !== 'android') { @@ -345,10 +366,10 @@ class StatusBar extends React.Component { * * @param props Object containing the StatusBar props to use in the stack entry. */ - static pushStackEntry(props: any): any { + static pushStackEntry(props: any, surface?: mixed): any { const entry = createStackEntry(props); StatusBar._propsStack.push(entry); - StatusBar._updatePropsStack(); + StatusBar._updatePropsStack(surface); return entry; } @@ -357,12 +378,12 @@ class StatusBar extends React.Component { * * @param entry Entry returned from `pushStackEntry`. */ - static popStackEntry(entry: any) { + static popStackEntry(entry: any, surface?: mixed) { const index = StatusBar._propsStack.indexOf(entry); if (index !== -1) { StatusBar._propsStack.splice(index, 1); } - StatusBar._updatePropsStack(); + StatusBar._updatePropsStack(surface); } /** @@ -371,13 +392,13 @@ class StatusBar extends React.Component { * @param entry Entry returned from `pushStackEntry` to replace. * @param props Object containing the StatusBar props to use in the replacement stack entry. */ - static replaceStackEntry(entry: any, props: any): any { + static replaceStackEntry(entry: any, props: any, surface?: mixed): any { const newEntry = createStackEntry(props); const index = StatusBar._propsStack.indexOf(entry); if (index !== -1) { StatusBar._propsStack[index] = newEntry; } - StatusBar._updatePropsStack(); + StatusBar._updatePropsStack(surface); return newEntry; } @@ -389,6 +410,9 @@ class StatusBar extends React.Component { showHideTransition: 'fade', }; + // $FlowFixMe (signature-verification-failure) + static contextType = RootTagContext; + _stackEntry = null; componentDidMount() { @@ -396,26 +420,27 @@ class StatusBar extends React.Component { // and always update the native status bar with the props from the top of then // stack. This allows having multiple StatusBar components and the one that is // added last or is deeper in the view hierarchy will have priority. - this._stackEntry = StatusBar.pushStackEntry(this.props); + this._stackEntry = StatusBar.pushStackEntry(this.props, this.context); } componentWillUnmount() { // When a StatusBar is unmounted, remove itself from the stack and update // the native bar with the next props. - StatusBar.popStackEntry(this._stackEntry); + StatusBar.popStackEntry(this._stackEntry, this.context); } componentDidUpdate() { this._stackEntry = StatusBar.replaceStackEntry( this._stackEntry, this.props, + this.context, ); } /** * Updates the native status bar with the props from the stack. */ - static _updatePropsStack = () => { + static _updatePropsStack = (surface?: mixed) => { // Send the update to the native module only once at the end of the frame. clearImmediate(StatusBar._updateImmediate); StatusBar._updateImmediate = setImmediate(() => { @@ -434,6 +459,7 @@ class StatusBar extends React.Component { NativeStatusBarManagerIOS.setStyle( mergedProps.barStyle.value, mergedProps.barStyle.animated || false, + ReactNative.findNodeHandle(surface) ?? -1, ); } if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) { @@ -442,6 +468,7 @@ class StatusBar extends React.Component { mergedProps.hidden.animated ? mergedProps.hidden.transition : 'none', + ReactNative.findNodeHandle(surface) ?? -1, ); } diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm index 93cd6113a2171a..c1296a3d2f6aae 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm @@ -2310,11 +2310,11 @@ + (RCTManagedPointer *)JS_NativeStatusBarManagerIOS_SpecGetHeightCallbackResult: } static facebook::jsi::Value __hostFunction_NativeStatusBarManagerIOSSpecJSI_setStyle(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { - return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "setStyle", @selector(setStyle:animated:), args, count); + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "setStyle", @selector(setStyle:animated:reactTag:), args, count); } static facebook::jsi::Value __hostFunction_NativeStatusBarManagerIOSSpecJSI_setHidden(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { - return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "setHidden", @selector(setHidden:withAnimation:), args, count); + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "setHidden", @selector(setHidden:withAnimation:reactTag:), args, count); } static facebook::jsi::Value __hostFunction_NativeStatusBarManagerIOSSpecJSI_getConstants(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { @@ -2337,10 +2337,10 @@ + (RCTManagedPointer *)JS_NativeStatusBarManagerIOS_SpecGetHeightCallbackResult: methodMap_["removeListeners"] = MethodMetadata {1, __hostFunction_NativeStatusBarManagerIOSSpecJSI_removeListeners}; - methodMap_["setStyle"] = MethodMetadata {2, __hostFunction_NativeStatusBarManagerIOSSpecJSI_setStyle}; + methodMap_["setStyle"] = MethodMetadata {3, __hostFunction_NativeStatusBarManagerIOSSpecJSI_setStyle}; - methodMap_["setHidden"] = MethodMetadata {2, __hostFunction_NativeStatusBarManagerIOSSpecJSI_setHidden}; + methodMap_["setHidden"] = MethodMetadata {3, __hostFunction_NativeStatusBarManagerIOSSpecJSI_setHidden}; methodMap_["getConstants"] = MethodMetadata {0, __hostFunction_NativeStatusBarManagerIOSSpecJSI_getConstants}; diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h index eb2b0c3416c422..3d309146c20706 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h @@ -117,6 +117,7 @@ namespace JS { folly::Optional cancelButtonIndex() const; folly::Optional anchor() const; folly::Optional tintColor() const; + folly::Optional reactTag() const; SpecShowActionSheetWithOptionsOptions(NSDictionary *const v) : _v(v) {} private: @@ -138,6 +139,7 @@ namespace JS { folly::Optional anchor() const; folly::Optional tintColor() const; folly::Optional> excludedActivityTypes() const; + folly::Optional reactTag() const; SpecShowShareActionSheetWithOptionsOptions(NSDictionary *const v) : _v(v) {} private: @@ -202,6 +204,7 @@ namespace JS { NSString *cancelButtonKey() const; NSString *destructiveButtonKey() const; NSString *keyboardType() const; + folly::Optional reactTag() const; Args(NSDictionary *const v) : _v(v) {} private: @@ -2483,9 +2486,11 @@ namespace JS { - (void)addListener:(NSString *)eventType; - (void)removeListeners:(double)count; - (void)setStyle:(NSString * _Nullable)statusBarStyle - animated:(BOOL)animated; + animated:(BOOL)animated + reactTag:(double)reactTag; - (void)setHidden:(BOOL)hidden - withAnimation:(NSString *)withAnimation; + withAnimation:(NSString *)withAnimation + reactTag:(double)reactTag; - (facebook::react::ModuleConstants)constantsToExport; - (facebook::react::ModuleConstants)getConstants; @@ -2934,6 +2939,11 @@ inline folly::Optional JS::NativeActionSheetManager::SpecShowActionSheet id const p = _v[@"tintColor"]; return RCTBridgingToOptionalDouble(p); } +inline folly::Optional JS::NativeActionSheetManager::SpecShowActionSheetWithOptionsOptions::reactTag() const +{ + id const p = _v[@"reactTag"]; + return RCTBridgingToOptionalDouble(p); +} inline NSString *JS::NativeActionSheetManager::SpecShowShareActionSheetWithOptionsOptions::message() const { id const p = _v[@"message"]; @@ -2964,6 +2974,11 @@ inline folly::Optional> JS::NativeAction id const p = _v[@"excludedActivityTypes"]; return RCTBridgingToOptionalVec(p, ^NSString *(id itemValue_0) { return RCTBridgingToString(itemValue_0); }); } +inline folly::Optional JS::NativeActionSheetManager::SpecShowShareActionSheetWithOptionsOptions::reactTag() const +{ + id const p = _v[@"reactTag"]; + return RCTBridgingToOptionalDouble(p); +} inline NSString *JS::NativeActionSheetManager::SpecShowShareActionSheetWithOptionsFailureCallbackError::domain() const { id const p = _v[@"domain"]; @@ -3024,6 +3039,11 @@ inline NSString *JS::NativeAlertManager::Args::keyboardType() const id const p = _v[@"keyboardType"]; return RCTBridgingToString(p); } +inline folly::Optional JS::NativeAlertManager::Args::reactTag() const +{ + id const p = _v[@"reactTag"]; + return RCTBridgingToOptionalDouble(p); +} inline bool JS::NativeAnimatedModule::EndResult::finished() const { id const p = _v[@"finished"]; diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index c01cdd8dbc942d..9b6b34fce36c58 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -11,6 +11,7 @@ 'use strict'; const AppContainer = require('../ReactNative/AppContainer'); +const RootTagContext = require('../ReactNative/RootTagContext'); const I18nManager = require('../ReactNative/I18nManager'); const PropTypes = require('prop-types'); const React = require('react'); @@ -146,10 +147,6 @@ class Modal extends React.Component { hardwareAccelerated: false, }; - static contextTypes: any | {|rootTag: React$PropType$Primitive|} = { - rootTag: PropTypes.number, - }; - _identifier: number; _eventSubscription: ?EmitterSubscription; @@ -217,9 +214,11 @@ class Modal extends React.Component { } const innerChildren = __DEV__ ? ( - - {this.props.children} - + + {rootTag => ( + {this.props.children} + )} + ) : ( this.props.children ); diff --git a/RNTester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js b/RNTester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js index c1130357829df5..15749a029dc097 100644 --- a/RNTester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js +++ b/RNTester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js @@ -169,7 +169,7 @@ class ShareActionSheetExample extends React.Component< (completed, method) => { let text; if (completed) { - text = `Shared via ${method}`; + text = `Shared via ${method || 'null'}`; } else { text = "You didn't share"; } @@ -212,7 +212,7 @@ class ShareScreenshotExample extends React.Component< (completed, method) => { let text; if (completed) { - text = `Shared via ${method}`; + text = `Shared via ${method || 'null'}`; } else { text = "You didn't share"; } @@ -270,7 +270,7 @@ class ShareScreenshotAnchorExample extends React.Component< (completed, method) => { let text; if (completed) { - text = `Shared via ${method}`; + text = `Shared via ${method || 'null'}`; } else { text = "You didn't share"; } diff --git a/RNTester/js/examples/Alert/AlertExample.js b/RNTester/js/examples/Alert/AlertExample.js index 4e362a525f91f9..5ffe67edb279b1 100644 --- a/RNTester/js/examples/Alert/AlertExample.js +++ b/RNTester/js/examples/Alert/AlertExample.js @@ -31,9 +31,13 @@ const alertMessage = type Props = $ReadOnly<{||}>; class SimpleAlertExampleBlock extends React.Component { + viewRef: React.MutableRefObject | void> = React.createRef(); + render() { return ( - + Alert.alert('Alert Title', alertMessage)}> @@ -120,6 +124,17 @@ class SimpleAlertExampleBlock extends React.Component { Alert without title + + Alert.alert('Surfaced!', alertMessage, null, { + surface: this.viewRef.current, + }) + }> + + Alert with surface passed + + ); } diff --git a/RNTester/js/examples/StatusBar/StatusBarExample.js b/RNTester/js/examples/StatusBar/StatusBarExample.js index 60510df9db0e9a..db90183885570a 100644 --- a/RNTester/js/examples/StatusBar/StatusBarExample.js +++ b/RNTester/js/examples/StatusBar/StatusBarExample.js @@ -11,7 +11,6 @@ 'use strict'; const React = require('react'); - const { StatusBar, StyleSheet, @@ -258,9 +257,14 @@ class StatusBarTranslucentExample extends React.Component< } class StatusBarStaticIOSExample extends React.Component<{...}> { + // $FlowFixMe + viewRef: React.MutableRefObject | void> = React.createRef(); + render() { return ( - + { @@ -279,6 +283,15 @@ class StatusBarStaticIOSExample extends React.Component<{...}> { setHidden(false, 'fade') + { + StatusBar.setHidden(true, 'fade', this.viewRef.current); + }}> + + setHidden(true, 'fade', componentRef) + + { @@ -297,6 +310,15 @@ class StatusBarStaticIOSExample extends React.Component<{...}> { setBarStyle('light-content', true) + { + StatusBar.setBarStyle('default', false, this.viewRef.current); + }}> + + setBarStyle('default', false, componentRef) + + { diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index c9a832d561c134..8d17823075548c 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -84,7 +84,7 @@ RCT_EXTERN UIWindow *__nullable RCTKeyWindow(void); // Returns the presented view controller, useful if you need // e.g. to present a modal view controller or alert over it -RCT_EXTERN UIViewController *__nullable RCTPresentedViewController(void); +RCT_EXTERN UIViewController *__nullable RCTPresentedViewController(UIWindow* _Nullable window); // Does this device support force touch (aka 3D Touch)? RCT_EXTERN BOOL RCTForceTouchAvailable(void); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 540b05ac23f74b..5c93d9540cebb1 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -500,13 +500,14 @@ BOOL RCTRunningInAppExtension(void) return RCTSharedApplication().keyWindow; } -UIViewController *__nullable RCTPresentedViewController(void) +UIViewController *__nullable RCTPresentedViewController(UIWindow* _Nullable window) { if ([RCTUtilsUIOverride hasPresentedViewController]) { return [RCTUtilsUIOverride presentedViewController]; } - UIViewController *controller = RCTKeyWindow().rootViewController; + UIWindow *keyWindow = window ?: RCTKeyWindow(); + UIViewController *controller = keyWindow.rootViewController; UIViewController *presentedController = controller.presentedViewController; while (presentedController && ![presentedController isBeingDismissed]) { controller = presentedController; diff --git a/React/CoreModules/RCTActionSheetManager.mm b/React/CoreModules/RCTActionSheetManager.mm index ca8713692973ad..1e3ee7e67f35bd 100644 --- a/React/CoreModules/RCTActionSheetManager.mm +++ b/React/CoreModules/RCTActionSheetManager.mm @@ -80,7 +80,10 @@ - (void)presentViewController:(UIViewController *)alertController destructiveButtonIndices = @[destructiveButtonIndex]; } - UIViewController *controller = RCTPresentedViewController(); + NSNumber *reactTag = options[@"reactTag"] ? [RCTConvert NSNumber:options[@"reactTag"]] : @-1; + UIView *view = [self.bridge.uiManager viewForReactTag:reactTag]; + UIViewController *controller = RCTPresentedViewController(view.window); + NSNumber *anchor = [RCTConvert NSNumber:options.anchor() ? @(*options.anchor()) : nil]; UIColor *tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil]; @@ -179,7 +182,10 @@ - (void)presentViewController:(UIViewController *)alertController shareController.excludedActivityTypes = excludedActivityTypes; } - UIViewController *controller = RCTPresentedViewController(); + NSNumber *reactTag = options[@"reactTag"] ? [RCTConvert NSNumber:options[@"reactTag"]] : @-1; + UIView *view = [self.bridge.uiManager viewForReactTag:reactTag]; + UIViewController *controller = RCTPresentedViewController(view.window); + shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) { if (activityError) { failureCallback(@[RCTJSErrorFromNSError(activityError)]); diff --git a/React/CoreModules/RCTAlertManager.mm b/React/CoreModules/RCTAlertManager.mm index 91c236a6da1b4c..0671170974a4c6 100644 --- a/React/CoreModules/RCTAlertManager.mm +++ b/React/CoreModules/RCTAlertManager.mm @@ -12,6 +12,7 @@ #import #import #import +#import #import #import "CoreModulesPlugins.h" @@ -36,6 +37,8 @@ @implementation RCTAlertManager NSHashTable *_alertControllers; } +@synthesize bridge = _bridge; + RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue @@ -75,6 +78,7 @@ - (void)invalidate NSString *cancelButtonKey = [RCTConvert NSString:args.cancelButtonKey()]; NSString *destructiveButtonKey = [RCTConvert NSString:args.destructiveButtonKey()]; UIKeyboardType keyboardType = [RCTConvert UIKeyboardType:args.keyboardType()]; + NSNumber *reactTag = args[@"reactTag"] ? [RCTConvert NSNumber:args[@"reactTag"]] : @-1; if (!title && !message) { RCTLogError(@"Must specify either an alert title, or message, or both"); @@ -94,21 +98,6 @@ - (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 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 @@ -191,6 +180,23 @@ - (void)invalidate [_alertControllers addObject:alertController]; dispatch_async(dispatch_get_main_queue(), ^{ + UIView *view = [self.bridge.uiManager viewForReactTag:reactTag]; + UIViewController *presentingController = RCTPresentedViewController(view.window); + + 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 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; + } + [presentingController presentViewController:alertController animated:YES completion:nil]; }); } diff --git a/React/CoreModules/RCTDevMenu.mm b/React/CoreModules/RCTDevMenu.mm index 69ff839faa7be8..2d275cb6ca9537 100644 --- a/React/CoreModules/RCTDevMenu.mm +++ b/React/CoreModules/RCTDevMenu.mm @@ -241,7 +241,7 @@ - (void)setDefaultJSBundle dismissViewControllerAnimated:YES completion:nil]; }]]; - [RCTPresentedViewController() presentViewController:alertController + [RCTPresentedViewController(nil) presentViewController:alertController animated:YES completion:NULL]; }]]; @@ -266,7 +266,7 @@ - (void)setDefaultJSBundle [RCTInspectorDevServerHelper attachDebugger:@"ReactNative" withBundleURL:bridge.bundleURL - withView:RCTPresentedViewController()]; + withView:RCTPresentedViewController(nil)]; #endif }]]; } @@ -313,7 +313,7 @@ - (void)setDefaultJSBundle dismissViewControllerAnimated:YES completion:nil]; }]]; - [RCTPresentedViewController() presentViewController:alertController + [RCTPresentedViewController(nil) presentViewController:alertController animated:YES completion:NULL]; } else { @@ -388,7 +388,7 @@ - (void)setDefaultJSBundle handler:^(__unused UIAlertAction *action) { return; }]]; - [RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL]; + [RCTPresentedViewController(nil) presentViewController:alertController animated:YES completion:NULL]; }]]; [items addObjectsFromArray:_extraMenuItems]; @@ -425,7 +425,7 @@ - (void)setDefaultJSBundle handler:[self alertActionHandlerForDevItem:nil]]]; _presentedItems = items; - [RCTPresentedViewController() presentViewController:_actionSheet animated:YES completion:nil]; + [RCTPresentedViewController(nil) presentViewController:_actionSheet animated:YES completion:nil]; [_bridge enqueueJSCall:@"RCTNativeAppEventEmitter" method:@"emit" diff --git a/React/CoreModules/RCTPerfMonitor.mm b/React/CoreModules/RCTPerfMonitor.mm index 3f710ae1333a6f..26be2e4a34df7e 100644 --- a/React/CoreModules/RCTPerfMonitor.mm +++ b/React/CoreModules/RCTPerfMonitor.mm @@ -322,9 +322,7 @@ - (void)show [self updateStats]; - UIWindow *window = RCTSharedApplication().delegate.window; - [window addSubview:self.container]; - + [RCTKeyWindow() addSubview:self.container]; _uiDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(threadUpdate:)]; diff --git a/React/CoreModules/RCTRedBox.mm b/React/CoreModules/RCTRedBox.mm index a83fc0b59d4e68..30ea7e57f923d3 100644 --- a/React/CoreModules/RCTRedBox.mm +++ b/React/CoreModules/RCTRedBox.mm @@ -246,6 +246,8 @@ - (void)dismiss { self.hidden = YES; [self resignFirstResponder]; + // TODO (T62270251): We shouldn't rely on UIApplicationDelegate window -- it's not guaranteed to be there + // and it's deprecated in iOS 13 [RCTSharedApplication().delegate.window makeKeyWindow]; } diff --git a/React/CoreModules/RCTStatusBarManager.mm b/React/CoreModules/RCTStatusBarManager.mm index 2f74040625cdfb..a1dac72080ed9c 100644 --- a/React/CoreModules/RCTStatusBarManager.mm +++ b/React/CoreModules/RCTStatusBarManager.mm @@ -8,8 +8,10 @@ #import "RCTStatusBarManager.h" #import "CoreModulesPlugins.h" +#import #import #import +#import #import #if !TARGET_OS_TV @@ -149,37 +151,50 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification } ]); } -RCT_EXPORT_METHOD(setStyle : (NSString *)style animated : (BOOL)animated) +RCT_EXPORT_METHOD(setStyle:(NSString *)style + animated:(BOOL)animated + reactTag:(nonnull __unused NSNumber *)reactTag) { UIStatusBarStyle statusBarStyle = [RCTConvert UIStatusBarStyle:style]; if (RCTViewControllerBasedStatusBarAppearance()) { RCTLogError(@"RCTStatusBarManager module requires that the \ UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - } else { + return; + } + + // TODO (T62270453): Add proper support for UIScenes (this requires view controller based status bar management) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [RCTSharedApplication() setStatusBarStyle:statusBarStyle animated:animated]; - } + [RCTSharedApplication() setStatusBarStyle:statusBarStyle + animated:animated]; #pragma clang diagnostic pop } -RCT_EXPORT_METHOD(setHidden : (BOOL)hidden withAnimation : (NSString *)withAnimation) +RCT_EXPORT_METHOD(setHidden:(BOOL)hidden + withAnimation:(NSString *)withAnimation + reactTag:(nonnull __unused NSNumber *)reactTag) { UIStatusBarAnimation animation = [RCTConvert UIStatusBarAnimation:withAnimation]; if (RCTViewControllerBasedStatusBarAppearance()) { RCTLogError(@"RCTStatusBarManager module requires that the \ UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - } else { + return; + } + + // TODO (T62270453): Add proper support for UIScenes (this requires view controller based status bar management) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [RCTSharedApplication() setStatusBarHidden:hidden withAnimation:animation]; + [RCTSharedApplication() setStatusBarHidden:hidden + withAnimation:animation]; #pragma clang diagnostic pop - } } RCT_EXPORT_METHOD(setNetworkActivityIndicatorVisible : (BOOL)visible) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" RCTSharedApplication().networkActivityIndicatorVisible = visible; +#pragma clang diagnostic pop } - (facebook::react::ModuleConstants)getConstants diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index d201f86a18485e..a43c77099e6c11 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -401,9 +401,9 @@ + (void)toggle:(UIButton *)target }; RCTProfileControlsWindow.hidden = YES; dispatch_async(dispatch_get_main_queue(), ^{ - [[[[RCTSharedApplication() delegate] window] rootViewController] presentViewController:activityViewController - animated:YES - completion:nil]; + [RCTPresentedViewController(nil) presentViewController:activityViewController + animated:YES + completion:nil]; }); #endif }); @@ -777,7 +777,7 @@ void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *data) [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]]; - [RCTPresentedViewController() presentViewController:alertController animated:YES completion:nil]; + [RCTPresentedViewController(nil) presentViewController:alertController animated:YES completion:nil]; }); #endif }