Skip to content

Instantly share code, notes, and snippets.

@larkintuckerllc
Created September 30, 2018 19:18
Show Gist options
  • Save larkintuckerllc/15644c314207df00c212ecb14b981439 to your computer and use it in GitHub Desktop.
Save larkintuckerllc/15644c314207df00c212ecb14b981439 to your computer and use it in GitHub Desktop.
hrnk 4
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,
};
@condini-mastheus
Copy link

That's a very clean solution, i'll try in a project and give you the feedback.

@ashish-dhiman
Copy link

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.

@switch13
Copy link

switch13 commented Jan 31, 2020

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);
}

@LyricL-Gitster
Copy link

Why is shift in state if it never changes?

@LyricL-Gitster
Copy link

LyricL-Gitster commented Oct 20, 2020

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;

@wilbertaristo
Copy link

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.

@jforaker
Copy link

@wilbertaristo you can replace it by grabbing the native id from the currentlyFocusedInput object:

  // replace currentlyFocusedField with this:
  const { _nativeTag } = TextInputState.currentlyFocusedInput();

@wilbertaristo
Copy link

@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();

@Biplovkumar
Copy link

@wilbertaristo can u please post here updated typescript class

@Biplovkumar
Copy link

cause of UIManager.measure also depricated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment