-
-
Save larkintuckerllc/15644c314207df00c212ecb14b981439 to your computer and use it in GitHub Desktop.
import { PropTypes } from 'prop-types'; | |
import React, { Component } from 'react'; | |
import { Animated, Dimensions, Keyboard, StyleSheet, TextInput, UIManager } from 'react-native'; | |
const { State: TextInputState } = TextInput; | |
export default class KeyboardShift extends Component { | |
state = { | |
shift: new Animated.Value(0), | |
}; | |
componentWillMount() { | |
this.keyboardDidShowSub = Keyboard.addListener('keyboardDidShow', this.handleKeyboardDidShow); | |
this.keyboardDidHideSub = Keyboard.addListener('keyboardDidHide', this.handleKeyboardDidHide); | |
} | |
componentWillUnmount() { | |
this.keyboardDidShowSub.remove(); | |
this.keyboardDidHideSub.remove(); | |
} | |
render() { | |
const { children: renderProp } = this.props; | |
const { shift } = this.state; | |
return ( | |
<Animated.View style={[styles.container, { transform: [{translateY: shift}] }]}> | |
{renderProp()} | |
</Animated.View> | |
); | |
} | |
handleKeyboardDidShow = (event) => { | |
const { height: windowHeight } = Dimensions.get('window'); | |
const keyboardHeight = event.endCoordinates.height; | |
const currentlyFocusedField = TextInputState.currentlyFocusedField(); | |
UIManager.measure(currentlyFocusedField, (originX, originY, width, height, pageX, pageY) => { | |
const fieldHeight = height; | |
const fieldTop = pageY; | |
const gap = (windowHeight - keyboardHeight) - (fieldTop + fieldHeight); | |
if (gap >= 0) { | |
return; | |
} | |
Animated.timing( | |
this.state.shift, | |
{ | |
toValue: gap, | |
duration: 1000, | |
useNativeDriver: true, | |
} | |
).start(); | |
}); | |
} | |
handleKeyboardDidHide = () => { | |
Animated.timing( | |
this.state.shift, | |
{ | |
toValue: 0, | |
duration: 1000, | |
useNativeDriver: true, | |
} | |
).start(); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
height: '100%', | |
left: 0, | |
position: 'absolute', | |
top: 0, | |
width: '100%' | |
} | |
}); | |
KeyboardShift.propTypes = { | |
children: PropTypes.func.isRequired, | |
}; |
Can we combine this with, returnKeyType={"next"}, because it works for lower part of UI when we first close the keyboard and then focus on the lower part TextInput.
any thoughts on updating for the newly deprecated componentWillMount?
constructor(props) {
super(props);
this.keyboardDidShowSub = Keyboard.addListener('keyboardDidShow', this.handleKeyboardDidShow);
this.keyboardDidHideSub = Keyboard.addListener('keyboardDidHide', this.handleKeyboardDidHide);
}
Why is shift
in state if it never changes?
Thanks for this btw. I've updated it for using with hooks and TS if anyone needs:
import React, {useEffect, useState} from "react";
import {
Animated,
Dimensions,
EmitterSubscription,
Keyboard,
KeyboardEvent,
StyleSheet,
TextInput,
UIManager,
} from "react-native";
const { State: TextInputState } = TextInput;
const handleKeyboardDidShow = (shift: Animated.Value, event: KeyboardEvent) => {
const { height: windowHeight } = Dimensions.get("window");
const keyboardHeight = event.endCoordinates.height;
const currentlyFocusedField = TextInputState.currentlyFocusedField();
UIManager.measure(currentlyFocusedField, (_, __, ___, height, ____, pageY) => {
const fieldHeight = height;
const fieldTop = pageY;
const gap = (windowHeight - keyboardHeight) - (fieldTop + fieldHeight);
if (gap >= 0) {
return;
}
Animated.timing(
shift,
{
toValue: gap,
duration: 1000,
useNativeDriver: true,
},
).start();
});
};
const handleKeyboardDidHide = (shift: Animated.Value) => {
Animated.timing(
shift,
{
toValue: 0,
duration: 1000,
useNativeDriver: true,
},
).start();
};
const KeyboardShift: React.FunctionComponent<{}> = ({children}) => {
const [keyboardDidShowSub, setKeyboardDidShowSub] = useState<EmitterSubscription>();
const [keyboardDidHideSub, setKeyboardDidHideSub] = useState<EmitterSubscription>();
const [shift] = useState(new Animated.Value(0));
useEffect(() => {
if (keyboardDidShowSub == null) {
setKeyboardDidShowSub(Keyboard.addListener("keyboardDidShow", handleKeyboardDidShow.bind(null, shift)));
}
if (keyboardDidHideSub == null) {
setKeyboardDidHideSub(Keyboard.addListener("keyboardDidHide", handleKeyboardDidHide.bind(null, shift)));
}
return () => {
keyboardDidShowSub?.remove();
keyboardDidHideSub?.remove();
};
}, [keyboardDidShowSub, keyboardDidHideSub]);
return (
<Animated.View style={[styles.container, { transform: [{translateY: shift}] }]}>
{children}
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
height: "100%",
left: 0,
position: "absolute",
top: 0,
width: "100%",
},
});
export default KeyboardShift;
hi @LyricL-Gitster thank you so much for the Hooks & TS code snippet!
The code works but there is an annoying error message every time i try to open a TextInput:
currentlyFocusedField is deprecated and will be removed in a future release. Use currentlyFocusedInput
I understand that currentlyFocusedField
would return the ID of the focused TextInput. However, currentlyFocusedInput
apparently returns the ref of the currently focused text field, so I can't simply replace currentlyFocusedField
with currentlyFocusedInput
. Is there any way for me to get the ID of the field using currentlyFocusedInput
?
I read online for solutions regarding this but there seems to be none that is clean. The solutions I read involves modifying node-modules
which is a very hacky way and I would like to avoid such methods.
@wilbertaristo you can replace it by grabbing the native id from the currentlyFocusedInput
object:
// replace currentlyFocusedField with this:
const { _nativeTag } = TextInputState.currentlyFocusedInput();
@jforaker that works! thank you so much :) btw to prevent TypeScript from giving me errors I modified the above into:
const { _nativeTag } = (TextInputState as any).currentlyFocusedInput();
@wilbertaristo can u please post here updated typescript class
cause of UIManager.measure also depricated.
That's a very clean solution, i'll try in a project and give you the feedback.