read

Note: This is a fairly untested, I just wanted to post this quickly to get the information out there. If there are bugs or you have a better way of handling this, please let me know in the comments below.

I’ve been pulling my hair out trying to get the keyboard to behave nicely in React Native for the past couple of days. Working cross platform can really be a pain because of how native components behave differently on each system.

On Android, the keyboard would either

  • Push the entire view up. This included the navigation bar which would end up off screen. Not ideal.
  • Push only the non-navigation elements of the view up, compressing the content between the nav elements and the keyboard. This is ideal.

On iOS, the keyboard wouldn’t move anything but instead cover up the view. If the input element was towards the bottom of the screen, then they would be covered up and the user wouldn’t be able to see what they were typing.

After trying a bunch of different ways of getting the desired behavior, I finally stumbled on something that works. First, to get Android working consistently, open up ./android/app/src/main/AndroidManifest.xml and add android:windowSoftInputMode="adjustResize" within the .MainActivity activity declaration. It should look something like:

 <activity
  android:name=".MainActivity"
  android:label="@string/app_name"
  android:windowSoftInputMode="adjustResize"
  android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
  <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>

Then, go ahead and grab the npm package react-native-keyboard-spacer. Once that’s installed, you’ll want to make a component that disables the spacing behavior on Android because the change to the manifest does all the work for you. Unfortunately, you’ll lose the nice animation that react-native-keyboard-spacer provides but I haven’t figured out a way to force Android to behave in the same way as iOS. Android likes to move things to make room for the keyboard whereas iOS just puts the keyboard over them.

Your PlatformAwareKeyboardSpacer.js is very straightforward:

import React, {
  Platform,
  View,
} from 'react-native';

import KeyboardSpacer from 'react-native-keyboard-spacer';


const PlatformAwareKeyboardSpacer = (props) => {
  if (Platform.OS === 'android') {
    return <View />;
  }

  return (
    <KeyboardSpacer {...props} />
  );
};

export default PlatformAwareKeyboardSpacer;

Now wherever you have an element that will bring up the keyboard, place your new <PlatformAwareKeyboardSpacer /> as the very last element in the render body of your component, like this:

<View>
  ...
  <PlatformAwareKeyboardSpacer />
</View>

Since you’re compressing things within your view, you might need to wrap some elements in a <ScrollView> to make sure that when the user launches the keyboard, they will still be able to scroll to access all parts of your View. Make sure you add the keyboardShouldPersistTaps prop to your ScrollView if the user will be filling out multiple inputs in your View.

Lastly, in iOS (but not in Android), bringing up the keyboard can push the focused input out of the user’s view. If your input is within a ScrollView and towards the bottom of the View, this is bound to happen. Fortunately StackOverflow is here to the save the day. Here’s what you want to do:

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

And on the input withing the ScrollView that is going off screen:

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

Note that is this unecessary for Android. I extracted this function into a util function and added

if (Platform.OS === 'ios') {
  ...
}

Around the timeout body.

That’s it! Hopefully this works for you and saves you some headache and frustration!

Blog Logo

Arjun Rao


Published

Image

Arjun Rao

A blog for things and such

Back to Overview