Skip to content

Commit de3457f

Browse files
Mehdi MulaniFacebook Github Bot 3
authored andcommitted
Allow <Modal /> to be presented in different orientations
Reviewed By: javache Differential Revision: D3760002 fbshipit-source-id: 01f5c246fb0fc041ec2d63b4ef80de858fb6fdf2
1 parent a13e1c4 commit de3457f

File tree

7 files changed

+112
-11
lines changed

7 files changed

+112
-11
lines changed

Examples/UIExplorer/js/ModalExample.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ var React = require('react');
2626
var ReactNative = require('react-native');
2727
var {
2828
Modal,
29+
Picker,
2930
StyleSheet,
3031
Switch,
3132
Text,
3233
TouchableHighlight,
3334
View,
3435
} = ReactNative;
36+
// $FlowFixMe Picker.Item not properly defined for flow.
37+
const Item = Picker.Item;
3538

3639
exports.displayName = (undefined: ?string);
3740
exports.framework = 'React';
@@ -68,11 +71,22 @@ class Button extends React.Component {
6871
}
6972
}
7073

74+
const supportedOrientationsPickerValues = [
75+
['portrait'],
76+
['landscape'],
77+
['landscape-left'],
78+
['portrait', 'landscape-right'],
79+
['portrait', 'landscape'],
80+
[],
81+
];
82+
7183
class ModalExample extends React.Component {
7284
state = {
7385
animationType: 'none',
7486
modalVisible: false,
7587
transparent: false,
88+
selectedSupportedOrientation: 0,
89+
currentOrientation: 'unknown',
7690
};
7791

7892
_setModalVisible = (visible) => {
@@ -104,11 +118,14 @@ class ModalExample extends React.Component {
104118
animationType={this.state.animationType}
105119
transparent={this.state.transparent}
106120
visible={this.state.modalVisible}
107-
onRequestClose={() => {this._setModalVisible(false)}}
121+
onRequestClose={() => this._setModalVisible(false)}
122+
supportedOrientations={supportedOrientationsPickerValues[this.state.selectedSupportedOrientation]}
123+
onOrientationChange={evt => this.setState({currentOrientation: evt.nativeEvent.orientation})}
108124
>
109125
<View style={[styles.container, modalBackgroundStyle]}>
110126
<View style={[styles.innerContainer, innerContainerTransparentStyle]}>
111127
<Text>This modal was presented {this.state.animationType === 'none' ? 'without' : 'with'} animation.</Text>
128+
<Text>It is currently displayed in {this.state.currentOrientation} mode.</Text>
112129
<Button
113130
onPress={this._setModalVisible.bind(this, false)}
114131
style={styles.modalButton}>
@@ -135,6 +152,22 @@ class ModalExample extends React.Component {
135152
<Switch value={this.state.transparent} onValueChange={this._toggleTransparent} />
136153
</View>
137154

155+
<View>
156+
<Text style={styles.rowTitle}>Supported orientations</Text>
157+
<Picker
158+
selectedValue={this.state.selectedSupportedOrientation}
159+
onValueChange={(_, i) => this.setState({selectedSupportedOrientation: i})}
160+
itemStyle={styles.pickerItem}
161+
>
162+
<Item label="Portrait" value={0} />
163+
<Item label="Landscape" value={1} />
164+
<Item label="Landscape left" value={2} />
165+
<Item label="Portrait and landscape right" value={3} />
166+
<Item label="Portrait and landscape" value={4} />
167+
<Item label="Default supportedOrientations" value={5} />
168+
</Picker>
169+
</View>
170+
138171
<Button onPress={this._setModalVisible.bind(this, true)}>
139172
Present
140173
</Button>
@@ -187,4 +220,7 @@ var styles = StyleSheet.create({
187220
modalButton: {
188221
marginTop: 10,
189222
},
223+
pickerItem: {
224+
fontSize: 16,
225+
},
190226
});

Libraries/Modal/Modal.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ class Modal extends React.Component {
111111
PropTypes.bool,
112112
'Use the `animationType` prop instead.'
113113
),
114+
/**
115+
* The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations.
116+
* On iOS, the modal is still restricted by what's specified in your app's Info.plist's UISupportedInterfaceOrientations field.
117+
* @platform ios
118+
*/
119+
supportedOrientations: PropTypes.arrayOf(PropTypes.oneOf(['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'])),
120+
/**
121+
* The `onOrientationChange` callback is called when the orientation changes while the modal is being displayed.
122+
* The orientation provided is only 'portrait' or 'landscape'. This callback is also called on initial render, regardless of the current orientation.
123+
* @platform ios
124+
*/
125+
onOrientationChange: PropTypes.func,
114126
};
115127

116128
static defaultProps = {
@@ -144,6 +156,8 @@ class Modal extends React.Component {
144156
onShow={this.props.onShow}
145157
style={styles.modal}
146158
onStartShouldSetResponder={this._shouldSetResponder}
159+
supportedOrientations={this.props.supportedOrientations}
160+
onOrientationChange={this.props.onOrientationChange}
147161
>
148162
<View style={[styles.container, containerStyles]}>
149163
{this.props.children}

React/Views/RCTModalHostView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727

2828
@property (nonatomic, weak) id<RCTModalHostViewInteractor> delegate;
2929

30+
@property (nonatomic, copy) NSArray<NSString *> *supportedOrientations;
31+
@property (nonatomic, copy) RCTDirectEventBlock onOrientationChange;
32+
3033
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
3134

3235
@end

React/Views/RCTModalHostView.m

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
#import "RCTUIManager.h"
1717
#import "UIView+React.h"
1818

19+
#import <UIKit/UIKit.h>
20+
1921
@implementation RCTModalHostView
2022
{
2123
__weak RCTBridge *_bridge;
2224
BOOL _isPresented;
2325
RCTModalHostViewController *_modalViewController;
2426
RCTTouchHandler *_touchHandler;
2527
UIView *_reactSubview;
28+
UIInterfaceOrientation _lastKnownOrientation;
2629
}
2730

2831
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
@@ -52,7 +55,28 @@ - (void)notifyForBoundsChange:(CGRect)newBounds
5255
{
5356
if (_reactSubview && _isPresented) {
5457
[_bridge.uiManager setFrame:newBounds forView:_reactSubview];
58+
[self notifyForOrientationChange];
59+
}
60+
}
61+
62+
- (void)notifyForOrientationChange
63+
{
64+
if (!_onOrientationChange) {
65+
return;
5566
}
67+
68+
UIInterfaceOrientation currentOrientation = [[UIApplication sharedApplication] statusBarOrientation];
69+
if (currentOrientation == _lastKnownOrientation) {
70+
return;
71+
}
72+
_lastKnownOrientation = currentOrientation;
73+
74+
BOOL isPortrait = currentOrientation == UIInterfaceOrientationPortrait || currentOrientation == UIInterfaceOrientationPortraitUpsideDown;
75+
NSDictionary *eventPayload =
76+
@{
77+
@"orientation": isPortrait ? @"portrait" : @"landscape",
78+
};
79+
_onOrientationChange(eventPayload);
5680
}
5781

5882
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
@@ -95,6 +119,7 @@ - (void)didMoveToWindow
95119
if (!_isPresented && self.window) {
96120
RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller");
97121

122+
_modalViewController.supportedInterfaceOrientations = [self supportedOrientationsMask];
98123
if ([self.animationType isEqualToString:@"fade"]) {
99124
_modalViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
100125
} else if ([self.animationType isEqualToString:@"slide"]) {
@@ -136,4 +161,31 @@ - (void)setTransparent:(BOOL)transparent
136161
_modalViewController.modalPresentationStyle = transparent ? UIModalPresentationCustom : UIModalPresentationFullScreen;
137162
}
138163

164+
- (UIInterfaceOrientationMask)supportedOrientationsMask
165+
{
166+
if (_supportedOrientations.count == 0) {
167+
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
168+
return UIInterfaceOrientationMaskAll;
169+
} else {
170+
return UIInterfaceOrientationMaskPortrait;
171+
}
172+
}
173+
174+
UIInterfaceOrientationMask supportedOrientations = 0;
175+
for (NSString *orientation in _supportedOrientations) {
176+
if ([orientation isEqualToString:@"portrait"]) {
177+
supportedOrientations |= UIInterfaceOrientationMaskPortrait;
178+
} else if ([orientation isEqualToString:@"portrait-upside-down"]) {
179+
supportedOrientations |= UIInterfaceOrientationMaskPortraitUpsideDown;
180+
} else if ([orientation isEqualToString:@"landscape"]) {
181+
supportedOrientations |= UIInterfaceOrientationMaskLandscape;
182+
} else if ([orientation isEqualToString:@"landscape-left"]) {
183+
supportedOrientations |= UIInterfaceOrientationMaskLandscapeLeft;
184+
} else if ([orientation isEqualToString:@"landscape-right"]) {
185+
supportedOrientations |= UIInterfaceOrientationMaskLandscapeRight;
186+
}
187+
}
188+
return supportedOrientations;
189+
}
190+
139191
@end

React/Views/RCTModalHostViewController.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@
1313

1414
@property (nonatomic, copy) void (^boundsDidChangeBlock)(CGRect newBounds);
1515

16+
@property (nonatomic, assign) UIInterfaceOrientationMask supportedInterfaceOrientations;
17+
1618
@end

React/Views/RCTModalHostViewController.m

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#import "RCTModalHostViewController.h"
1111

12+
#import "RCTModalHostView.h"
13+
1214
@implementation RCTModalHostViewController
1315
{
1416
CGRect _lastViewFrame;
@@ -38,16 +40,6 @@ - (void)viewDidLayoutSubviews
3840
}
3941
}
4042

41-
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
42-
{
43-
// Picking some defaults here, we should probably make this configurable
44-
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
45-
return UIInterfaceOrientationMaskAll;
46-
} else {
47-
return UIInterfaceOrientationMaskPortrait;
48-
}
49-
}
50-
5143
- (UIStatusBarStyle)preferredStatusBarStyle
5244
{
5345
return _preferredStatusBarStyle;

React/Views/RCTModalHostViewManager.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,7 @@ - (void)invalidate
9595
RCT_EXPORT_VIEW_PROPERTY(animationType, NSString)
9696
RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL)
9797
RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)
98+
RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray)
99+
RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock)
98100

99101
@end

0 commit comments

Comments
 (0)