Skip to content

Commit 7d19ff3

Browse files
author
Alex Akers
committed
Add <Modal /> component
Summary: Create Modal component that can be used to present content modally.
1 parent f53c95c commit 7d19ff3

File tree

14 files changed

+359
-8
lines changed

14 files changed

+359
-8
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* The examples provided by Facebook are for non-commercial testing and
3+
* evaluation purposes only.
4+
*
5+
* Facebook reserves all rights not expressly granted.
6+
*
7+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
8+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9+
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
10+
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
11+
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
12+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
*
14+
* @flow
15+
*/
16+
'use strict';
17+
18+
var React = require('react-native');
19+
var {
20+
Modal,
21+
StyleSheet,
22+
Text,
23+
TouchableHighlight,
24+
View,
25+
} = React;
26+
27+
exports.displayName = (undefined: ?string);
28+
exports.framework = 'React';
29+
exports.title = '<Modal>';
30+
exports.description = 'Component for presenting modal views.';
31+
32+
var ModalExample = React.createClass({
33+
getInitialState: function() {
34+
return {
35+
openModal: null,
36+
};
37+
},
38+
39+
_closeModal: function() {
40+
this.setState({openModal: null});
41+
},
42+
43+
_openAnimatedModal: function() {
44+
this.setState({openModal: 'animated'});
45+
},
46+
47+
_openNotAnimatedModal: function() {
48+
this.setState({openModal: 'not-animated'});
49+
},
50+
51+
render: function() {
52+
return (
53+
<View>
54+
<Modal animated={true} visible={this.state.openModal === 'animated'}>
55+
<View style={styles.container}>
56+
<Text>This modal was presented with animation.</Text>
57+
<TouchableHighlight underlayColor="#a9d9d4" onPress={this._closeModal}>
58+
<Text>Close</Text>
59+
</TouchableHighlight>
60+
</View>
61+
</Modal>
62+
63+
<Modal visible={this.state.openModal === 'not-animated'}>
64+
<View style={styles.container}>
65+
<Text>This modal was presented immediately, without animation.</Text>
66+
<TouchableHighlight underlayColor="#a9d9d4" onPress={this._closeModal}>
67+
<Text>Close</Text>
68+
</TouchableHighlight>
69+
</View>
70+
</Modal>
71+
72+
<TouchableHighlight underlayColor="#a9d9d4" onPress={this._openAnimatedModal}>
73+
<Text>Present Animated</Text>
74+
</TouchableHighlight>
75+
76+
<TouchableHighlight underlayColor="#a9d9d4" onPress={this._openNotAnimatedModal}>
77+
<Text>Present Without Animation</Text>
78+
</TouchableHighlight>
79+
</View>
80+
);
81+
},
82+
});
83+
84+
exports.examples = [
85+
{
86+
title: 'Modal Presentation',
87+
description: 'Modals can be presented with or without animation',
88+
render: () => <ModalExample />,
89+
},
90+
];
91+
92+
var styles = StyleSheet.create({
93+
container: {
94+
alignItems: 'center',
95+
backgroundColor: '#f5fcff',
96+
flex: 1,
97+
justifyContent: 'center',
98+
},
99+
});

Examples/UIExplorer/UIExplorerList.ios.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var COMPONENTS = [
3737
require('./ListViewGridLayoutExample'),
3838
require('./ListViewPagingExample'),
3939
require('./MapViewExample'),
40+
require('./ModalExample'),
4041
require('./Navigator/NavigatorExample'),
4142
require('./NavigatorIOSColorsExample'),
4243
require('./NavigatorIOSExample'),

Libraries/Modal/Modal.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule Modal
10+
* @flow
11+
*/
12+
'use strict';
13+
14+
var React = require('React');
15+
var StyleSheet = require('StyleSheet');
16+
var View = require('View');
17+
18+
var requireNativeComponent = require('requireNativeComponent');
19+
var RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
20+
21+
class Modal extends React.Component {
22+
render(): ?ReactElement {
23+
if (this.props.visible === false) {
24+
return null;
25+
}
26+
27+
return (
28+
<RCTModalHostView animated={this.props.animated} style={styles.modal}>
29+
<View style={styles.container}>
30+
{this.props.children}
31+
</View>
32+
</RCTModalHostView>
33+
);
34+
}
35+
}
36+
37+
var styles = StyleSheet.create({
38+
modal: {
39+
position: 'absolute',
40+
},
41+
container: {
42+
left: 0,
43+
position: 'absolute',
44+
top: 0,
45+
}
46+
});
47+
48+
module.exports = Modal;

Libraries/react-native/react-native.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
2424
Image: require('Image'),
2525
ListView: require('ListView'),
2626
MapView: require('MapView'),
27+
Modal: require('Modal'),
2728
Navigator: require('Navigator'),
2829
NavigatorIOS: require('NavigatorIOS'),
2930
PickerIOS: require('PickerIOS'),

React/Base/RCTRootView.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ - (void)setFrame:(CGRect)frame
261261
{
262262
super.frame = frame;
263263
if (self.reactTag && _bridge.isValid) {
264-
[_bridge.uiManager setFrame:frame forRootView:self];
264+
[_bridge.uiManager setFrame:frame forView:self];
265265
}
266266
}
267267

React/Modules/RCTUIManager.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@
4040
- (UIView *)viewForReactTag:(NSNumber *)reactTag;
4141

4242
/**
43-
* Update the frame of a root view. This might be in response to a screen rotation
43+
* Update the frame of a view. This might be in response to a screen rotation
4444
* or some other layout event outside of the React-managed view hierarchy.
4545
*/
46-
- (void)setFrame:(CGRect)frame forRootView:(UIView *)rootView;
46+
- (void)setFrame:(CGRect)frame forView:(UIView *)view;
4747

4848
/**
4949
* Update the background color of a root view. This is usually triggered by

React/Modules/RCTUIManager.m

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,13 +368,11 @@ - (UIView *)viewForReactTag:(NSNumber *)reactTag
368368
return _viewRegistry[reactTag];
369369
}
370370

371-
- (void)setFrame:(CGRect)frame forRootView:(UIView *)rootView
371+
- (void)setFrame:(CGRect)frame forView:(UIView *)view
372372
{
373373
RCTAssertMainThread();
374374

375-
NSNumber *reactTag = rootView.reactTag;
376-
RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag);
377-
375+
NSNumber *reactTag = view.reactTag;
378376
dispatch_async(_shadowQueue, ^{
379377
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
380378
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);

React/React.xcodeproj/project.pbxproj

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */; };
6868
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
6969
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
70+
83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */; };
71+
83A1FE8C1B62640A00BE0E65 /* RCTModalHostView.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A1FE8B1B62640A00BE0E65 /* RCTModalHostView.m */; };
72+
83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */; };
7073
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */; };
7174
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */; };
7275
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA501A601E3B00E9B192 /* RCTUtils.m */; };
@@ -218,6 +221,12 @@
218221
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
219222
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
220223
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
224+
83392EB11B6634E10013B15F /* RCTModalHostViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModalHostViewController.h; sourceTree = "<group>"; };
225+
83392EB21B6634E10013B15F /* RCTModalHostViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModalHostViewController.m; sourceTree = "<group>"; };
226+
83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModalHostView.h; sourceTree = "<group>"; };
227+
83A1FE8B1B62640A00BE0E65 /* RCTModalHostView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModalHostView.m; sourceTree = "<group>"; };
228+
83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModalHostViewManager.h; sourceTree = "<group>"; };
229+
83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModalHostViewManager.m; sourceTree = "<group>"; };
221230
83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = "<group>"; };
222231
83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArray.m; sourceTree = "<group>"; };
223232
83CBBA2E1A601D0E00E9B192 /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -317,6 +326,12 @@
317326
14435CE21AAC4AE100FC20F4 /* RCTMap.m */,
318327
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */,
319328
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */,
329+
83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */,
330+
83A1FE8B1B62640A00BE0E65 /* RCTModalHostView.m */,
331+
83392EB11B6634E10013B15F /* RCTModalHostViewController.h */,
332+
83392EB21B6634E10013B15F /* RCTModalHostViewController.m */,
333+
83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */,
334+
83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */,
320335
13B0800C1A69489C00A75B9A /* RCTNavigator.h */,
321336
13B0800D1A69489C00A75B9A /* RCTNavigator.m */,
322337
13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */,
@@ -359,6 +374,7 @@
359374
137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */,
360375
137327E51AA5CF210034F82E /* RCTTabBarManager.h */,
361376
137327E61AA5CF210034F82E /* RCTTabBarManager.m */,
377+
E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */,
362378
13E0674F1A70F44B002CDEE1 /* RCTView.h */,
363379
13E067501A70F44B002CDEE1 /* RCTView.m */,
364380
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */,
@@ -373,7 +389,6 @@
373389
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */,
374390
13E067531A70F44B002CDEE1 /* UIView+React.h */,
375391
13E067541A70F44B002CDEE1 /* UIView+React.m */,
376-
E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */,
377392
);
378393
path = Views;
379394
sourceTree = "<group>";
@@ -607,19 +622,22 @@
607622
13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */,
608623
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */,
609624
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */,
625+
83A1FE8C1B62640A00BE0E65 /* RCTModalHostView.m in Sources */,
610626
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */,
611627
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */,
612628
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */,
613629
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
614630
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
615631
00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */,
616632
63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */,
633+
83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */,
617634
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
618635
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
619636
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
620637
1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */,
621638
1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */,
622639
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
640+
83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */,
623641
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
624642
138D6A141B53CD290074A87E /* RCTCache.m in Sources */,
625643
13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */,

React/Views/RCTModalHostView.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <UIKit/UIKit.h>
11+
12+
@class RCTBridge;
13+
14+
@interface RCTModalHostView : UIView
15+
16+
@property (nonatomic, assign, getter=isAnimated) BOOL animated;
17+
18+
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
19+
20+
@end

React/Views/RCTModalHostView.m

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTModalHostView.h"
11+
12+
#import "RCTAssert.h"
13+
#import "RCTBridge.h"
14+
#import "RCTModalHostViewController.h"
15+
#import "RCTTouchHandler.h"
16+
#import "RCTUIManager.h"
17+
#import "UIView+React.h"
18+
19+
@implementation RCTModalHostView
20+
{
21+
RCTBridge *_bridge;
22+
BOOL _hasModalView;
23+
RCTModalHostViewController *_modalViewController;
24+
RCTTouchHandler *_touchHandler;
25+
}
26+
27+
RCT_NOT_IMPLEMENTED(-initWithFrame:(CGRect)frame)
28+
29+
- (instancetype)initWithBridge:(RCTBridge *)bridge
30+
{
31+
if ((self = [super initWithFrame:CGRectZero])) {
32+
_bridge = bridge;
33+
_modalViewController = [[RCTModalHostViewController alloc] init];
34+
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge];
35+
36+
__weak RCTModalHostView *weakSelf = self;
37+
_modalViewController.boundsDidChangeBlock = ^(CGRect newBounds) {
38+
[weakSelf notifyForBoundsChange:newBounds];
39+
};
40+
}
41+
42+
return self;
43+
}
44+
45+
- (void)notifyForBoundsChange:(CGRect)newBounds
46+
{
47+
if (_hasModalView) {
48+
[_bridge.uiManager setFrame:newBounds forView:_modalViewController.view];
49+
}
50+
}
51+
52+
- (NSArray *)reactSubviews
53+
{
54+
return _hasModalView ? @[_modalViewController.view] : @[];
55+
}
56+
57+
- (void)insertReactSubview:(UIView *)subview atIndex:(__unused NSInteger)atIndex
58+
{
59+
[subview addGestureRecognizer:_touchHandler];
60+
_modalViewController.view = subview;
61+
_hasModalView = YES;
62+
}
63+
64+
- (void)removeReactSubview:(UIView *)subview
65+
{
66+
RCTAssert(subview == _modalViewController.view, @"Cannot remove view other than modal view");
67+
_modalViewController.view = nil;
68+
_hasModalView = NO;
69+
}
70+
71+
- (void)didMoveToSuperview
72+
{
73+
[super didMoveToSuperview];
74+
75+
if (self.superview) {
76+
[self.backingViewController presentViewController:_modalViewController animated:self.animated completion:nil];
77+
} else {
78+
[_modalViewController dismissViewControllerAnimated:self.animated completion:nil];
79+
}
80+
}
81+
82+
@end

0 commit comments

Comments
 (0)