How to auto-slide the window out from behind keyboard when TextInput has focus?

JavascriptReact Native

Javascript Problem Overview


I've seen this hack for native apps to auto scroll the window, but wondering best way to do it in React Native... When a <TextInput> field gets focus and is positioned low in the view, the keyboard will cover up the text field.

You can see this issue in example UIExplorer's TextInputExample.js view.

Does anyone have a good solution?

Javascript Solutions


Solution 1 - Javascript

2017 Answer

The KeyboardAvoidingView is probably the best way to go now. Check out the docs here. It is really simple compared to Keyboard module which gives Developer more control to perform animations. Spencer Carli demonstrated all the possible ways on his medium blog.

2015 Answer

The correct way to do this in react-native does not require external libraries, takes advantage of native code, and includes animations.

First define a function that will handle the onFocus event for each TextInput (or any other component you would like to scroll to):

// Scroll a component into view. Just pass the component ref string.
inputFocused (refName) {
  setTimeout(() => {
    let scrollResponder = this.refs.scrollView.getScrollResponder();
    scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
      React.findNodeHandle(this.refs[refName]),
      110, //additionalOffset
      true
    );
  }, 50);
}

Then, in your render function:

render () {
  return (
    <ScrollView ref='scrollView'>
        <TextInput ref='username' 
                   onFocus={this.inputFocused.bind(this, 'username')}
    </ScrollView>
  )
}

This uses the RCTDeviceEventEmitter for keyboard events and sizing, measures the position of the component using RCTUIManager.measureLayout, and calculates the exact scroll movement required in scrollResponderInputMeasureAndScrollToKeyboard.

You may want to play around with the additionalOffset parameter, to fit the needs of your specific UI design.

Solution 2 - Javascript

Facebook open sourced KeyboardAvoidingView in react native 0.29 to solve this problem. Documentation and usage example can be found here.

Solution 3 - Javascript

We combined some of the code form react-native-keyboard-spacer and the code from @Sherlock to create a KeyboardHandler component that can be wrapped around any View with TextInput elements. Works like a charm! :-)

/**
 * Handle resizing enclosed View and scrolling to input
 * Usage:
 *    <KeyboardHandler ref='kh' offset={50}>
 *      <View>
 *        ...
 *        <TextInput ref='username'
 *          onFocus={()=>this.refs.kh.inputFocused(this,'username')}/>
 *        ...
 *      </View>
 *    </KeyboardHandler>
 * 
 *  offset is optional and defaults to 34
 *  Any other specified props will be passed on to ScrollView
 */
'use strict';

var React=require('react-native');
var {
  ScrollView,
  View,
  DeviceEventEmitter,
}=React;


var myprops={ 
  offset:34,
}
var KeyboardHandler=React.createClass({
  propTypes:{
    offset: React.PropTypes.number,
  },
  getDefaultProps(){
    return myprops;
  },
  getInitialState(){
    DeviceEventEmitter.addListener('keyboardDidShow',(frames)=>{
      if (!frames.endCoordinates) return;
      this.setState({keyboardSpace: frames.endCoordinates.height});
    });
    DeviceEventEmitter.addListener('keyboardWillHide',(frames)=>{
      this.setState({keyboardSpace:0});
    });

    this.scrollviewProps={
      automaticallyAdjustContentInsets:true,
      scrollEventThrottle:200,
    };
    // pass on any props we don't own to ScrollView
    Object.keys(this.props).filter((n)=>{return n!='children'})
    .forEach((e)=>{if(!myprops[e])this.scrollviewProps[e]=this.props[e]});

    return {
      keyboardSpace:0,
    };
  },
  render(){
    return (
      <ScrollView ref='scrollView' {...this.scrollviewProps}>
        {this.props.children}
        <View style={{height:this.state.keyboardSpace}}></View>
      </ScrollView>
    );
  },
  inputFocused(_this,refName){
    setTimeout(()=>{
      let scrollResponder=this.refs.scrollView.getScrollResponder();
      scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
        React.findNodeHandle(_this.refs[refName]),
        this.props.offset, //additionalOffset
        true
      );
    }, 50);
  }
}) // KeyboardHandler

module.exports=KeyboardHandler;

Solution 4 - Javascript

First you need to install react-native-keyboardevents.

  1. In XCode, in the project navigator, right click Libraries ➜ Add Files to [your project's name] Go to node_modules ➜ react-native-keyboardevents and add the .xcodeproj file
  2. In XCode, in the project navigator, select your project. Add the lib*.a from the keyboardevents project to your project's Build Phases ➜ Link Binary With Libraries Click .xcodeproj file you added before in the project navigator and go the Build Settings tab. Make sure 'All' is toggled on (instead of 'Basic'). Look for Header Search Paths and make sure it contains both $(SRCROOT)/../react-native/React and $(SRCROOT)/../../React - mark both as recursive.
  3. Run your project (Cmd+R)

Then back in javascript land:

You need to import the react-native-keyboardevents.

var KeyboardEvents = require('react-native-keyboardevents');
var KeyboardEventEmitter = KeyboardEvents.Emitter;

Then in your view, add some state for the keyboard space and update from listening to the keyboard events.

  getInitialState: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, (frames) => {
      this.setState({keyboardSpace: frames.end.height});
    });
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, (frames) => {
      this.setState({keyboardSpace: 0});
    });

    return {
      keyboardSpace: 0,
    };
  },

Finally, add a spacer to your render function beneath everything so when it increases size it bumps your stuff up.

<View style={{height: this.state.keyboardSpace}}></View>

It is also possible to use the animation api, but for simplicity's sake we just adjust after the animation.

Solution 5 - Javascript

react-native-keyboard-aware-scroll-view solved the problem for me. react-native-keyboard-aware-scroll-view on GitHub

Solution 6 - Javascript

Try this:

import React, {
  DeviceEventEmitter,
  Dimensions
} from 'react-native';

...

getInitialState: function() {
  return {
    visibleHeight: Dimensions.get('window').height
  }
},

...

componentDidMount: function() {
  let self = this;

  DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    self.keyboardWillShow(e);
  });

  DeviceEventEmitter.addListener('keyboardWillHide', function(e: Event) {
      self.keyboardWillHide(e);
  });
}

...

keyboardWillShow (e) {
  let newSize = Dimensions.get('window').height - e.endCoordinates.height;
  this.setState({visibleHeight: newSize});
},

keyboardWillHide (e) {
  this.setState({visibleHeight: Dimensions.get('window').height});
},

...

render: function() {
  return (<View style={{height: this.state.visibleHeight}}>your view code here...</View>);
}

...

It worked for me. The view basically shrinks when the keyboard is displayed, and grows back again when its hidden.

Solution 7 - Javascript

Just wanted to mention, now there is a KeyboardAvoidingView in RN. Just import it and use it as any other module in RN.

Here is the link to the commit on RN:

https://github.com/facebook/react-native/commit/8b78846a9501ef9c5ce9d1e18ee104bfae76af2e

It is available from 0.29.0

They have also included an example on UIExplorer.

Solution 8 - Javascript

Maybe is to late, but the best solution is to use a native library, IQKeyboardManager

Just drag and drop IQKeyboardManager directory from demo project to your iOS project. That's it. Also you can setup some valus, as isToolbar enabled, or the space between text input and keyboard in the AppDelegate.m file. More details about customisation are in the GitHub page link that I've added.

Solution 9 - Javascript

I used TextInput.onFocus and ScrollView.scrollTo.

...
<ScrollView ref="scrollView">
...
<TextInput onFocus={this.scrolldown}>
...
scrolldown: function(){
  this.refs.scrollView.scrollTo(width*2/3);
},

Solution 10 - Javascript

@Stephen

If you don't mind not having the height animate at exactly the same rate that the keyboard appears, you can just use LayoutAnimation, so that at least the height doesn't jump into place. e.g.

import LayoutAnimation from react-native and add the following methods to your component.

getInitialState: function() {
    return {keyboardSpace: 0};
  },
   updateKeyboardSpace: function(frames) {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: frames.end.height});
  },

  resetKeyboardSpace: function() {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: 0});
  },

  componentDidMount: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

  componentWillUnmount: function() {
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

Some example animations are (I'm using the spring one above):

var animations = {
  layout: {
    spring: {
      duration: 400,
      create: {
        duration: 300,
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.opacity,
      },
      update: {
        type: LayoutAnimation.Types.spring,
        springDamping: 400,
      },
    },
    easeInEaseOut: {
      duration: 400,
      create: {
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.scaleXY,
      },
      update: {
        type: LayoutAnimation.Types.easeInEaseOut,
      },
    },
  },
};

UPDATE:

See @sherlock's answer below, as of react-native 0.11 the keyboard resizing can be solved using built in functionality.

Solution 11 - Javascript

You can combine a few of the methods into something a little simpler.

Attach a onFocus listener on your inputs

<TextInput ref="password" secureTextEntry={true} 
           onFocus={this.scrolldown.bind(this,'password')}
/>

Our scroll down method looks something like :

scrolldown(ref) {
    const self = this;
    this.refs[ref].measure((ox, oy, width, height, px, py) => {
        self.refs.scrollView.scrollTo({y: oy - 200});
    });
}

This tells our scroll view (remember to add a ref) to scroll to down to the position of our focused input - 200 (it's roughly the size of the keyboard)

componentWillMount() {
    this.keyboardDidHideListener = Keyboard.addListener(
      'keyboardWillHide', 
      this.keyboardDidHide.bind(this)
    )
}

componentWillUnmount() {
    this.keyboardDidHideListener.remove()
}

keyboardDidHide(e) {
    this.refs.scrollView.scrollTo({y: 0});
}

Here we reset our scroll view back to the top,

enter image description here

Solution 12 - Javascript

I'm using a simpler method, but it's not animated yet. I have a component state called "bumpedUp" which I default to 0, but set to 1 when the textInput gets focus, like this:

On my textInput:

onFocus={() => this.setState({bumpedUp: 1})}
onEndEditing={() => this.setState({bumpedUp: 0})}

I also have style that gives the wrapping container of everything on that screen a bottom margin and negative top margin, like this:

mythingscontainer: {
  flex: 1,
  justifyContent: "center",
  alignItems: "center",
  flexDirection: "column",
},
bumpedcontainer: {
  marginBottom: 210,
  marginTop: -210,
},

And then on the wrapping container, I set the styles like this:

<View style={[styles.mythingscontainer, this.state.bumpedUp && styles.bumpedcontainer]}>

So, when the "bumpedUp" state gets set to 1, the bumpedcontainer style kicks in and moves the content up.

Kinda hacky and the margins are hardcoded, but it works :)

Solution 13 - Javascript

I use brysgo answer to raise the bottom of my scrollview. Then I use the onScroll to update the current position of the scrollview. I then found this https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element to get the position of the textinput. I then do some simple math to figure out if the input is in the current view. Then I use scrollTo to move the minimum amount plus a margin. It's pretty smooth. Heres the code for the scrolling portion:

		    focusOn: function(target) {
		    	return () => {
		    		var handle = React.findNodeHandle(this.refs[target]);
					UIManager.measureLayoutRelativeToParent( handle, 
					 	(e) => {console.error(e)}, 
					  	(x,y,w,h) => {
					    	var offs = this.scrollPosition + 250;
					    	var subHeaderHeight = (Sizes.width > 320) ? Sizes.height * 0.067 : Sizes.height * 0.077;
					    	var headerHeight = Sizes.height / 9;
					    	var largeSpace = (Sizes.height - (subHeaderHeight + headerHeight));
					    	var shortSpace = largeSpace - this.keyboardOffset;
					    	if(y+h >= this.scrollPosition + shortSpace) {
					    		this.refs.sv.scrollTo(y+h - shortSpace + 20);
					    	}
					    	if(y < this.scrollPosition) this.refs.sv.scrollTo(this.scrollPosition - (this.scrollPosition-y) - 20 );
					 	}
					 );
		    	};
		    },

Solution 14 - Javascript

I also meet this question. Finally, I resolve it by defining the height of each scene, such as:

<Navigator
    ...
    sceneStyle={{height: **}}
/>

And, I also use a third-party module https://github.com/jaysoo/react-native-extra-dimensions-android to get the real height.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMcGView Question on Stackoverflow
Solution 1 - JavascriptSherlockView Answer on Stackoverflow
Solution 2 - JavascriptfarwayerView Answer on Stackoverflow
Solution 3 - JavascriptJohn kendallView Answer on Stackoverflow
Solution 4 - JavascriptbrysgoView Answer on Stackoverflow
Solution 5 - JavascriptamirflView Answer on Stackoverflow
Solution 6 - JavascriptpomoView Answer on Stackoverflow
Solution 7 - JavascriptAakash SigdelView Answer on Stackoverflow
Solution 8 - JavascriptAdrian ZghibartaView Answer on Stackoverflow
Solution 9 - Javascriptshohey1226View Answer on Stackoverflow
Solution 10 - JavascriptdeanmcphersonView Answer on Stackoverflow
Solution 11 - JavascriptNathView Answer on Stackoverflow
Solution 12 - JavascriptStirmanView Answer on Stackoverflow
Solution 13 - JavascriptaintnorestView Answer on Stackoverflow
Solution 14 - JavascriptRenguang DongView Answer on Stackoverflow