iOS `presentationStyle` Modal Appearance

Summary:
When using `<Modal` on larger iOS devices, esp. iPad and iPhone 7 Plus devices, there is no way to use the system functionality for controlling the appearance of modals (`presentationStyle`), which improves the native system's animation and display of smaller content appearing within large horizontal space.

I've added a new picker for selecting a `presentationStyle` within  the RNTester app. See below for the appearance of this change, as well as the relevant changes to the RN documentation.

![may-22-2017 09-49-50](https://cloud.githubusercontent.com/assets/3521186/26315020/6d4b1cb0-3ed5-11e7-8ac8-a996f1ee00f9.gif)
<img width="1051" alt="screen shot 2017-05-22 at 9 50 12 am" src="https://cloud.githubusercontent.com/assets/3521186/26315021/6d4cbf7a-3ed5-11e7-9d13-a5d20c9f3533.png">
Closes https://github.com/facebook/react-native/pull/14102

Differential Revision: D5281990

Pulled By: shergin

fbshipit-source-id: 882d8cb79e7adb0b4437cdf26e5e7ab1fc04f4c1
This commit is contained in:
Hank Brekke 2017-06-20 19:02:27 -07:00 committed by Facebook Github Bot
parent 70ca432e5a
commit ec68536e08
6 changed files with 85 additions and 0 deletions

View File

@ -78,6 +78,7 @@ const RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
* }
* ```
*/
class Modal extends React.Component {
static propTypes = {
/**
@ -90,6 +91,19 @@ class Modal extends React.Component {
* Default is set to `none`.
*/
animationType: PropTypes.oneOf(['none', 'slide', 'fade']),
/**
* The `presentationStyle` prop controls how the modal appears (generally on larger devices such as iPad or plus-sized iPhones).
* See https://developer.apple.com/reference/uikit/uimodalpresentationstyle for details.
* @platform ios
*
* - `fullScreen` covers the screen completely
* - `pageSheet` covers portrait-width view centered (only on larger devices)
* - `formSheet` covers narrow-width view centered (only on larger devices)
* - `overFullScreen` covers the screen completely, but allows transparency
*
* Default is set to `overFullScreen` or `fullScreen` depending on `transparent` property.
*/
presentationStyle: PropTypes.oneOf(['fullScreen', 'pageSheet', 'formSheet', 'overFullScreen']),
/**
* The `transparent` prop determines whether your modal will fill the entire view. Setting this to `true` will render the modal over a transparent background.
*/
@ -119,6 +133,7 @@ class Modal extends React.Component {
/**
* The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations.
* On iOS, the modal is still restricted by what's specified in your app's Info.plist's UISupportedInterfaceOrientations field.
* When using `presentationStyle` of `pageSheet` or `formSheet`, this property will be ignored by iOS.
* @platform ios
*/
supportedOrientations: PropTypes.arrayOf(PropTypes.oneOf(['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'])),
@ -139,6 +154,21 @@ class Modal extends React.Component {
rootTag: PropTypes.number,
};
constructor(props: Object) {
super(props);
Modal._confirmProps(props);
}
componentWillReceiveProps(nextProps: Object) {
Modal._confirmProps(nextProps);
}
static _confirmProps(props: Object) {
if (props.presentationStyle && props.presentationStyle !== 'overFullScreen' && props.transparent) {
console.warn(`Modal with '${props.presentationStyle}' presentation style and 'transparent' value is not supported.`);
}
}
render(): ?React.Element<any> {
if (this.props.visible === false) {
return null;
@ -157,6 +187,14 @@ class Modal extends React.Component {
}
}
let presentationStyle = this.props.presentationStyle;
if (!presentationStyle) {
presentationStyle = 'fullScreen';
if (this.props.transparent) {
presentationStyle = 'overFullScreen';
}
}
const innerChildren = __DEV__ ?
( <AppContainer rootTag={this.context.rootTag}>
{this.props.children}
@ -166,6 +204,7 @@ class Modal extends React.Component {
return (
<RCTModalHostView
animationType={animationType}
presentationStyle={presentationStyle}
transparent={this.props.transparent}
hardwareAccelerated={this.props.hardwareAccelerated}
onRequestClose={this.props.onRequestClose}

View File

@ -74,6 +74,7 @@ class ModalExample extends React.Component {
animationType: 'none',
modalVisible: false,
transparent: false,
presentationStyle: 'fullScreen',
selectedSupportedOrientation: 0,
currentOrientation: 'unknown',
};
@ -105,6 +106,7 @@ class ModalExample extends React.Component {
<View>
<Modal
animationType={this.state.animationType}
presentationStyle={this.state.presentationStyle}
transparent={this.state.transparent}
visible={this.state.modalVisible}
onRequestClose={() => this._setModalVisible(false)}
@ -141,6 +143,21 @@ class ModalExample extends React.Component {
<Switch value={this.state.transparent} onValueChange={this._toggleTransparent} />
</View>
<View>
<Text style={styles.rowTitle}>Presentation style</Text>
<Picker
selectedValue={this.state.presentationStyle}
onValueChange={(presentationStyle) => this.setState({presentationStyle})}
itemStyle={styles.pickerItem}
>
<Item label="Full Screen" value="fullScreen" />
<Item label="Page Sheet" value="pageSheet" />
<Item label="Form Sheet" value="formSheet" />
<Item label="Over Full Screen" value="overFullScreen" />
<Item label="Default presentationStyle" value={null} />
</Picker>
</View>
<View>
<Text style={styles.rowTitle}>Supported orientations</Text>
<Picker

View File

@ -21,6 +21,7 @@
@interface RCTModalHostView : UIView <RCTInvalidating>
@property (nonatomic, copy) NSString *animationType;
@property (nonatomic, assign) UIModalPresentationStyle presentationStyle;
@property (nonatomic, assign, getter=isTransparent) BOOL transparent;
@property (nonatomic, copy) RCTDirectEventBlock onShow;

View File

@ -132,6 +132,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
} else if ([self.animationType isEqualToString:@"slide"]) {
_modalViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
}
if (self.presentationStyle != UIModalPresentationNone) {
_modalViewController.modalPresentationStyle = self.presentationStyle;
}
[_delegate presentModalHostView:self withViewController:_modalViewController animated:[self hasAnimationType]];
_isPresented = YES;
}
@ -165,6 +168,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
- (void)setTransparent:(BOOL)transparent
{
if (self.isTransparent != transparent) {
return;
}
_modalViewController.modalPresentationStyle = transparent ? UIModalPresentationOverFullScreen : UIModalPresentationFullScreen;
}

View File

@ -9,6 +9,13 @@
#import <React/RCTInvalidating.h>
#import <React/RCTViewManager.h>
#import <React/RCTConvert.h>
@interface RCTConvert (RCTModalHostView)
+ (UIModalPresentationStyle)UIModalPresentationStyle:(id)json;
@end
typedef void (^RCTModalViewInteractionBlock)(UIViewController *reactViewController, UIViewController *viewController, BOOL animated, dispatch_block_t completionBlock);

View File

@ -15,6 +15,19 @@
#import "RCTShadowView.h"
#import "RCTUtils.h"
@implementation RCTConvert (RCTModalHostView)
RCT_ENUM_CONVERTER(UIModalPresentationStyle, (@{
@"fullScreen": @(UIModalPresentationFullScreen),
#if !TARGET_OS_TV
@"pageSheet": @(UIModalPresentationPageSheet),
@"formSheet": @(UIModalPresentationFormSheet),
#endif
@"overFullScreen": @(UIModalPresentationOverFullScreen),
}), UIModalPresentationFullScreen, integerValue)
@end
@interface RCTModalHostShadowView : RCTShadowView
@end
@ -91,6 +104,7 @@ RCT_EXPORT_MODULE()
}
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(supportedOrientations, NSArray)