Skip to content

Commit 000446c

Browse files
committed
Keyboard support enhancements
1 parent 096da14 commit 000446c

File tree

3 files changed

+92
-63
lines changed

3 files changed

+92
-63
lines changed

Example/index.ios.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
StyleSheet,
1010
ScrollView,
1111
View,
12-
Dimensions
12+
Dimensions,
13+
TextInput
1314
} from 'react-native';
1415

1516
var screen = Dimensions.get('window');
@@ -59,6 +60,7 @@ class Example extends React.Component {
5960
<Button onPress={() => this.refs.modal4.open()} style={styles.btn}>Position bottom + backdrop + slider</Button>
6061
<Button onPress={() => this.setState({isOpen: true})} style={styles.btn}>Backdrop + backdropContent</Button>
6162
<Button onPress={() => this.refs.modal6.open()} style={styles.btn}>Position bottom + ScrollView</Button>
63+
<Button onPress={() => this.refs.modal7.open()} style={styles.btn}>Modal with keyboard support</Button>
6264

6365
<Modal
6466
style={[styles.modal, styles.modal1]}
@@ -96,6 +98,12 @@ class Example extends React.Component {
9698
</View>
9799
</ScrollView>
98100
</Modal>
101+
102+
<Modal ref={"modal7"} style={[styles.modal, styles.modal4]} position={"center"}>
103+
<View>
104+
<TextInput style={{height: 50, width: 200, backgroundColor: '#DDDDDD'}}/>
105+
</View>
106+
</Modal>
99107
</View>
100108
);
101109
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Check [index.js](https://github.com/maxs15/react-native-modalbox/blob/master/Exa
3737
| backButtonClose | false | `bool` | (Android only) Close modal when receiving back button event
3838
| startOpen | false | `bool` | Allow modal to appear open without animation upon first mount
3939
| coverScreen | false | `bool` | Will use RN `Modal` component to cover the entire screen wherever the modal is mounted in the component hierarchy
40+
| keyboardTopOffset | ios:22, android:0 | `number` | This property prevent the modal to cover the ios status bar when the modal is scrolling up because the keyboard is opening
4041

4142
## Events
4243

index.js

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ var {
1212
BackAndroid,
1313
BackHandler,
1414
Platform,
15-
Modal
15+
Modal,
16+
Keyboard
1617
} = require('react-native');
1718

1819
var BackButton = BackHandler || BackAndroid;
@@ -60,6 +61,7 @@ var ModalBox = React.createClass({
6061
backButtonClose: React.PropTypes.bool,
6162
easing: React.PropTypes.func,
6263
coverScreen: React.PropTypes.bool,
64+
keyboardTopOffset: React.PropTypes.number,
6365

6466
onClosed: React.PropTypes.func,
6567
onOpened: React.PropTypes.func,
@@ -81,6 +83,7 @@ var ModalBox = React.createClass({
8183
backButtonClose: false,
8284
easing: Easing.elastic(0.8),
8385
coverScreen: false,
86+
keyboardTopOffset: Platform.OS == 'ios' ? 22 : 0
8487
};
8588
},
8689

@@ -97,19 +100,30 @@ var ModalBox = React.createClass({
97100
width: screen.width,
98101
containerHeight: screen.height,
99102
containerWidth: screen.width,
100-
isInitialized: false
103+
isInitialized: false,
104+
keyboardOffset: 0
101105
};
102106
},
103107

104108
onBackPress () {
105-
this.close()
106-
return true
109+
this.close()
110+
return true
107111
},
108112

109113
componentWillMount: function() {
110114
this.createPanResponder();
111115
this.handleOpenning(this.props);
116+
// Needed for IOS because the keyboard covers the screen
117+
if (Platform.OS === 'ios') {
118+
this.subscriptions = [
119+
Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange),
120+
Keyboard.addListener('keyboardDidHide', this.onKeyboardHide)
121+
];
122+
}
123+
},
112124

125+
componentWillUnmount: function() {
126+
if (this.subscriptions) this.subscriptions.forEach((sub) => sub.remove());
113127
},
114128

115129
componentWillReceiveProps: function(props) {
@@ -128,6 +142,26 @@ var ModalBox = React.createClass({
128142

129143
/****************** ANIMATIONS **********************/
130144

145+
/*
146+
* The keyboard is hidden (IOS only)
147+
*/
148+
onKeyboardHide: function(evt) {
149+
this.state.keyboardOffset = 0;
150+
},
151+
152+
/*
153+
* The keyboard frame changed, used to detect when the keyboard open, faster than keyboardDidShow (IOS only)
154+
*/
155+
onKeyboardChange: function(evt) {
156+
if (!evt) return;
157+
if (!this.state.isOpen) return;
158+
var keyboardFrame = evt.endCoordinates;
159+
var keyboardHeight = this.state.containerHeight - keyboardFrame.screenY;
160+
161+
this.state.keyboardOffset = keyboardHeight;
162+
this.animateOpen();
163+
},
164+
131165
/*
132166
* Open animation for the backdrop, will fade in
133167
*/
@@ -196,8 +230,10 @@ var ModalBox = React.createClass({
196230

197231
requestAnimationFrame(() => {
198232
// Detecting modal position
199-
this.state.positionDest = this.calculateModalPosition(this.state.containerHeight, this.state.containerWidth);
200-
233+
this.state.positionDest = this.calculateModalPosition(this.state.containerHeight - this.state.keyboardOffset, this.state.containerWidth);
234+
if (this.state.keyboardOffset && (this.state.positionDest < this.props.keyboardTopOffset)) {
235+
this.state.positionDest = this.props.keyboardTopOffset;
236+
}
201237
this.state.animOpen = Animated.timing(
202238
this.state.position,
203239
{
@@ -207,9 +243,9 @@ var ModalBox = React.createClass({
207243
}
208244
);
209245
this.state.animOpen.start(() => {
246+
if (!this.state.isOpen && this.props.onOpened) this.props.onOpened();
210247
this.state.isAnimateOpen = false;
211248
this.state.isOpen = true;
212-
if (this.props.onOpened) this.props.onOpened();
213249
});
214250
})
215251

@@ -244,6 +280,7 @@ var ModalBox = React.createClass({
244280
}
245281
);
246282
this.state.animClose.start(() => {
283+
Keyboard.dismiss();
247284
this.state.isAnimateClose = false;
248285
this.state.isOpen = false;
249286
this.setState({});
@@ -344,43 +381,28 @@ var ModalBox = React.createClass({
344381
return;
345382
}
346383

347-
var modalPosition = this.calculateModalPosition(height, width);
348-
var coords = {};
349-
350-
// Fixing the position if the modal was already open or an animation was in progress
351-
if (this.state.isInitialized && (this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose)) {
352-
var position = this.state.isOpen ? modalPosition : this.state.containerHeight;
353-
354-
// Checking if a animation was in progress
355-
if (this.state.isAnimateOpen) {
356-
position = modalPosition;
357-
this.stopAnimateOpen();
358-
} else if (this.state.isAnimateClose) {
359-
position = this.state.containerHeight;
360-
this.stopAnimateClose();
361-
}
362-
this.state.position.setValue(position);
363-
coords = {positionDest: position};
384+
if (this.state.isOpen || this.state.isAnimateOpen) {
385+
this.animateOpen();
364386
}
365387

388+
if (this.props.onLayout) this.props.onLayout(evt);
366389
this.setState({
367390
isInitialized: true,
368391
containerHeight: height,
369-
containerWidth: width,
370-
...coords
392+
containerWidth: width
371393
});
372394
},
373395

374396
/*
375397
* Render the backdrop element
376398
*/
377-
renderBackdrop: function(size) {
378-
var backdrop = [];
399+
renderBackdrop: function() {
400+
var backdrop = null;
379401

380402
if (this.props.backdrop) {
381403
backdrop = (
382404
<TouchableWithoutFeedback onPress={this.props.backdropPressToClose ? this.close : null}>
383-
<Animated.View style={[styles.absolute, size, {opacity: this.state.backdropOpacity}]}>
405+
<Animated.View style={[styles.absolute, {opacity: this.state.backdropOpacity}]}>
384406
<View style={[styles.absolute, {backgroundColor:this.props.backdropColor, opacity: this.props.backdropOpacity}]}/>
385407
{this.props.backdropContent || []}
386408
</Animated.View>
@@ -391,45 +413,43 @@ var ModalBox = React.createClass({
391413
return backdrop;
392414
},
393415

416+
renderContent() {
417+
var size = {height: this.state.containerHeight, width: this.state.containerWidth};
418+
var offsetX = (this.state.containerWidth - this.state.width) / 2;
419+
420+
return (
421+
<Animated.View
422+
onLayout={this.onViewLayout}
423+
style={[styles.wrapper, size, this.props.style, {transform: [{translateY: this.state.position}, {translateX: offsetX}]} ]}
424+
{...this.state.pan.panHandlers}>
425+
{this.props.children}
426+
</Animated.View>
427+
)
428+
},
429+
394430
/*
395431
* Render the component
396432
*/
397433
render: function() {
398-
var visible = this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose;
399-
var size = {height: this.state.containerHeight, width: this.state.containerWidth};
400-
var offsetX = (this.state.containerWidth - this.state.width) / 2;
401-
var backdrop = this.renderBackdrop(size);
402-
var coverScreen = this.props.coverScreen;
403-
var modalContainer = [];
404-
405-
modalContainer = (
406-
<View style={{ flex: 1 }} pointerEvents={'box-none'} onLayout={this.onContainerLayout}>
407-
{backdrop}
408-
<Animated.View
409-
onLayout={this.onViewLayout}
410-
style={[styles.wrapper, size, this.props.style, {transform: [{translateY: this.state.position}, {translateX: offsetX}]} ]}
411-
{...this.state.pan.panHandlers}>
412-
{this.props.children}
413-
</Animated.View>
414-
</View>
415-
);
416-
434+
var visible = this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose;
435+
417436
if (!visible) return <View/>
418-
419-
if (coverScreen) {
420-
return (
421-
<Modal onRequestClose={() => this.close()} supportedOrientations={['landscape', 'portrait']} transparent visible={visible}>
422-
<View style={[styles.transparent, styles.absolute]}>
423-
{modalContainer}
424-
</View>
425-
</Modal>
426-
);
427-
}
437+
438+
var content = (
439+
<View style={[styles.transparent, styles.absolute]} pointerEvents={'box-none'}>
440+
<View style={{ flex: 1 }} onLayout={this.onContainerLayout}>
441+
{visible && this.renderBackdrop()}
442+
{visible && this.renderContent()}
443+
</View>
444+
</View>
445+
)
446+
447+
if (!this.props.coverScreen) return content;
428448

429449
return (
430-
<View style={[styles.transparent, styles.absolute]}>
431-
{modalContainer}
432-
</View>
450+
<Modal onRequestClose={() => this.close()} supportedOrientations={['landscape', 'portrait']} transparent visible={visible}>
451+
{content}
452+
</Modal>
433453
);
434454
},
435455

@@ -459,4 +479,4 @@ var ModalBox = React.createClass({
459479

460480
});
461481

462-
module.exports = ModalBox;
482+
module.exports = ModalBox;

0 commit comments

Comments
 (0)