From de3457f31dfd944f28f784bb98411a7b5ead8835 Mon Sep 17 00:00:00 2001 From: Mehdi Mulani Date: Wed, 7 Sep 2016 06:06:12 -0700 Subject: [PATCH] Allow to be presented in different orientations Reviewed By: javache Differential Revision: D3760002 fbshipit-source-id: 01f5c246fb0fc041ec2d63b4ef80de858fb6fdf2 --- Examples/UIExplorer/js/ModalExample.js | 38 ++++++++++++++++- Libraries/Modal/Modal.js | 14 +++++++ React/Views/RCTModalHostView.h | 3 ++ React/Views/RCTModalHostView.m | 52 ++++++++++++++++++++++++ React/Views/RCTModalHostViewController.h | 2 + React/Views/RCTModalHostViewController.m | 12 +----- React/Views/RCTModalHostViewManager.m | 2 + 7 files changed, 112 insertions(+), 11 deletions(-) diff --git a/Examples/UIExplorer/js/ModalExample.js b/Examples/UIExplorer/js/ModalExample.js index 522cfc135..34212f021 100644 --- a/Examples/UIExplorer/js/ModalExample.js +++ b/Examples/UIExplorer/js/ModalExample.js @@ -26,12 +26,15 @@ var React = require('react'); var ReactNative = require('react-native'); var { Modal, + Picker, StyleSheet, Switch, Text, TouchableHighlight, View, } = ReactNative; +// $FlowFixMe Picker.Item not properly defined for flow. +const Item = Picker.Item; exports.displayName = (undefined: ?string); exports.framework = 'React'; @@ -68,11 +71,22 @@ class Button extends React.Component { } } +const supportedOrientationsPickerValues = [ + ['portrait'], + ['landscape'], + ['landscape-left'], + ['portrait', 'landscape-right'], + ['portrait', 'landscape'], + [], +]; + class ModalExample extends React.Component { state = { animationType: 'none', modalVisible: false, transparent: false, + selectedSupportedOrientation: 0, + currentOrientation: 'unknown', }; _setModalVisible = (visible) => { @@ -104,11 +118,14 @@ class ModalExample extends React.Component { animationType={this.state.animationType} transparent={this.state.transparent} visible={this.state.modalVisible} - onRequestClose={() => {this._setModalVisible(false)}} + onRequestClose={() => this._setModalVisible(false)} + supportedOrientations={supportedOrientationsPickerValues[this.state.selectedSupportedOrientation]} + onOrientationChange={evt => this.setState({currentOrientation: evt.nativeEvent.orientation})} > This modal was presented {this.state.animationType === 'none' ? 'without' : 'with'} animation. + It is currently displayed in {this.state.currentOrientation} mode. @@ -187,4 +220,7 @@ var styles = StyleSheet.create({ modalButton: { marginTop: 10, }, + pickerItem: { + fontSize: 16, + }, }); diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index b91200132..43c1b3113 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -111,6 +111,18 @@ class Modal extends React.Component { PropTypes.bool, 'Use the `animationType` prop instead.' ), + /** + * 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. + * @platform ios + */ + supportedOrientations: PropTypes.arrayOf(PropTypes.oneOf(['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'])), + /** + * The `onOrientationChange` callback is called when the orientation changes while the modal is being displayed. + * The orientation provided is only 'portrait' or 'landscape'. This callback is also called on initial render, regardless of the current orientation. + * @platform ios + */ + onOrientationChange: PropTypes.func, }; static defaultProps = { @@ -144,6 +156,8 @@ class Modal extends React.Component { onShow={this.props.onShow} style={styles.modal} onStartShouldSetResponder={this._shouldSetResponder} + supportedOrientations={this.props.supportedOrientations} + onOrientationChange={this.props.onOrientationChange} > {this.props.children} diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h index 07b995eb5..dbf0556ff 100644 --- a/React/Views/RCTModalHostView.h +++ b/React/Views/RCTModalHostView.h @@ -27,6 +27,9 @@ @property (nonatomic, weak) id delegate; +@property (nonatomic, copy) NSArray *supportedOrientations; +@property (nonatomic, copy) RCTDirectEventBlock onOrientationChange; + - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; @end diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m index c622b1899..f82726bd3 100644 --- a/React/Views/RCTModalHostView.m +++ b/React/Views/RCTModalHostView.m @@ -16,6 +16,8 @@ #import "RCTUIManager.h" #import "UIView+React.h" +#import + @implementation RCTModalHostView { __weak RCTBridge *_bridge; @@ -23,6 +25,7 @@ RCTModalHostViewController *_modalViewController; RCTTouchHandler *_touchHandler; UIView *_reactSubview; + UIInterfaceOrientation _lastKnownOrientation; } RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) @@ -52,9 +55,30 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) { if (_reactSubview && _isPresented) { [_bridge.uiManager setFrame:newBounds forView:_reactSubview]; + [self notifyForOrientationChange]; } } +- (void)notifyForOrientationChange +{ + if (!_onOrientationChange) { + return; + } + + UIInterfaceOrientation currentOrientation = [[UIApplication sharedApplication] statusBarOrientation]; + if (currentOrientation == _lastKnownOrientation) { + return; + } + _lastKnownOrientation = currentOrientation; + + BOOL isPortrait = currentOrientation == UIInterfaceOrientationPortrait || currentOrientation == UIInterfaceOrientationPortraitUpsideDown; + NSDictionary *eventPayload = + @{ + @"orientation": isPortrait ? @"portrait" : @"landscape", + }; + _onOrientationChange(eventPayload); +} + - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex { RCTAssert(_reactSubview == nil, @"Modal view can only have one subview"); @@ -95,6 +119,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) if (!_isPresented && self.window) { RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller"); + _modalViewController.supportedInterfaceOrientations = [self supportedOrientationsMask]; if ([self.animationType isEqualToString:@"fade"]) { _modalViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; } else if ([self.animationType isEqualToString:@"slide"]) { @@ -136,4 +161,31 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder) _modalViewController.modalPresentationStyle = transparent ? UIModalPresentationCustom : UIModalPresentationFullScreen; } +- (UIInterfaceOrientationMask)supportedOrientationsMask +{ + if (_supportedOrientations.count == 0) { + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + return UIInterfaceOrientationMaskAll; + } else { + return UIInterfaceOrientationMaskPortrait; + } + } + + UIInterfaceOrientationMask supportedOrientations = 0; + for (NSString *orientation in _supportedOrientations) { + if ([orientation isEqualToString:@"portrait"]) { + supportedOrientations |= UIInterfaceOrientationMaskPortrait; + } else if ([orientation isEqualToString:@"portrait-upside-down"]) { + supportedOrientations |= UIInterfaceOrientationMaskPortraitUpsideDown; + } else if ([orientation isEqualToString:@"landscape"]) { + supportedOrientations |= UIInterfaceOrientationMaskLandscape; + } else if ([orientation isEqualToString:@"landscape-left"]) { + supportedOrientations |= UIInterfaceOrientationMaskLandscapeLeft; + } else if ([orientation isEqualToString:@"landscape-right"]) { + supportedOrientations |= UIInterfaceOrientationMaskLandscapeRight; + } + } + return supportedOrientations; +} + @end diff --git a/React/Views/RCTModalHostViewController.h b/React/Views/RCTModalHostViewController.h index ceadfbcc5..f0a2e24d7 100644 --- a/React/Views/RCTModalHostViewController.h +++ b/React/Views/RCTModalHostViewController.h @@ -13,4 +13,6 @@ @property (nonatomic, copy) void (^boundsDidChangeBlock)(CGRect newBounds); +@property (nonatomic, assign) UIInterfaceOrientationMask supportedInterfaceOrientations; + @end diff --git a/React/Views/RCTModalHostViewController.m b/React/Views/RCTModalHostViewController.m index 73c8ee736..2a1c94f88 100644 --- a/React/Views/RCTModalHostViewController.m +++ b/React/Views/RCTModalHostViewController.m @@ -9,6 +9,8 @@ #import "RCTModalHostViewController.h" +#import "RCTModalHostView.h" + @implementation RCTModalHostViewController { CGRect _lastViewFrame; @@ -38,16 +40,6 @@ } } -- (UIInterfaceOrientationMask)supportedInterfaceOrientations -{ - // Picking some defaults here, we should probably make this configurable - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { - return UIInterfaceOrientationMaskAll; - } else { - return UIInterfaceOrientationMaskPortrait; - } -} - - (UIStatusBarStyle)preferredStatusBarStyle { return _preferredStatusBarStyle; diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index ccd0cdca0..b228e0bde 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -95,5 +95,7 @@ RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(animationType, NSString) RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL) RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray) +RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock) @end