React-native: KeyboardAvoidingView has no effect on multiline TextInput

Created on 15 Nov 2017  ·  60Comments  ·  Source: facebook/react-native

KeyboardAvoidingView only works with single-line TextInputs. When the multiline prop is set, KeyboardAvoidingView does not shift the TextInput at all.

Is this a bug report?

Yes

Have you read the Contributing Guidelines?

Yes

Environment

Environment:
OS: macOS Sierra 10.12.6
Node: 7.0.0
npm: 3.10.8
Watchman: 4.7.0
Xcode: 9.1

Packages: (wanted => installed)
react-native: 0.49.3
react: 16.0.0-beta.5

Target Platform: iOS (10.3)

Steps to Reproduce

  1. Use a <TextInput> component with multiline prop set.
  2. Wrap this in a ScrollView
  3. Wrap that in a KeyboardAvoidingView.

Expected Behavior

Multiline TextInput should scroll above the soft keyboard.

Actual Behavior

Soft keyboard covers multiline TextInput.

Reproducible Demo

import React, { Component } from 'react';
import { Text, TextInput, View, ScrollView, KeyboardAvoidingView, Keyboard} from 'react-native';

...

    render() {
        return (
            <KeyboardAvoidingView style={{flex:1}} behavior="padding" keyboardVerticalOffset={64}>
                <ScrollView keyboardShouldPersistTaps={'handled'}>
                    <View style={{padding: 12}}>
                        // various content to fill the page
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 1</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 2</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 3</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 4</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 5</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 6</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 7</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 8</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 9</Text>
                        <Text style={{fontSize: 20, padding: 40}}>MESSAGE 10</Text>
                    </View>
                    <TextInput
                        style={{padding: 4}}
                        multiline={true}
                        placeholder={'Type something here...'}
                        onChangeText={this.updateMessage}
                        value={this.state.message}
                    />
                </ScrollView>
            </KeyboardAvoidingView>
        );
    }

Bug TextInput iOS Ran Commands

Most helpful comment

If you want to KeyboardAvoidingView works fine with multiline TextInput,
please setting the scrollEnabled prop of TextInput to false.
It works for me.

All 60 comments

I have a similar issue

Environment

node: 8.9.1
react: 16.0.0
react-native: 0.50.3
yarn: 1.3.2
watchman: 4.9.0
xcode: 9.1

Steps to Reproduce

  1. Use a <TextInput> component with autoCorrect="false" prop.
  2. Wrap that in a KeyboardAvoidingView with behavior="position" prop.
  3. Change keyboard type to Emoji keyboard

Expected Behavior

Multiline TextInput should be above the soft keyboard.

Actual Behavior

Soft keyboard covers TextInput.

Reproducible Demo

https://github.com/vivalaakam/react-native-issue

Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange) doesn't`t fire when I change keyboard to emoji and fires when I change from emoji to other

KeyboardAvoidingView works fine with multiline TextInput because i am using them myself. Initially they would not work for me. To solve my problem i removed the height on the TextInput and set the behavior to "padding". The "flex: 1" you have on the KeyboardAvoidingView might be the problem i think or its the scrollview. But KeyboardAvoidingView definitely works fine with multiline.

Referring to my snippet posted above, there's no height on the TextInput, and KeyboardAvoidingView's behavior is set to padding.

Its style needs flex:1 to allow the ScrollView to take up the entire height.

Try the code with and without multiline set. It fails with multiline set, but the same code works for single line TextInputs.

Same problem here!

@peacechen I have exactly the same problem like you. Need to add flex: 1 for the ScrollView as the wrapper. And it works if I remove the prop multiline={true}. Any solution for this?

The same issue。
my code is same as the author. there is a multiline TextInput in ScrollView. outsider with a KeyboardAvoidingView wrapper.

If anyone still is looking for a fix, you could try

<View style={{position:relative}}>
    <TextInput ref='multilineText' value={this.state.textValue} 
        onChangeText={(textValue) => this.setState({textValue})}/>
    <TextInput style={{ color:'transparent', position: 'absolute', width: '100%', height: '100%' }}
        placeholderTextColor='transparent'
        onFocus={() => setTimeout(() => this.refs.multilineText.focus(), 1000) } 
        value={this.state.textValue}
        onChangeText={(textValue) => this.setState({textValue})}
    />
</View>

Its a dirty hack but it does the job, what it does it when user clicks on the input it would be clicking on the single line and the keyboard will scroll to that line then after 1 seconds (you can adjust this, i just found 1 seconds to be good with our app) it would focus on the multiline. The value and onChangeText makes sure that when the user star typing they would have the same value except the single line has transparent text.

The downside is when a user clicked the multiple line, the cursor wont go to the location they clicked but it would go to the end.

@monmonja That's a creative work-around. I wonder if it could also be done by setting the multiline prop to a state variable that is changed during onFocus. That would eliminate the second TextInput.

I don't see the multiline prop in the first TextInput, but I assume it's supposed to be in there.

Has anyone found a good solution for this?

@peacechen I tried to implement your suggestion but it has a strange effect and doesn't work. Something's flickering and the keyboard eventually disappears.
I am also looking for a solution to this. Cannot apply @monmonja 's workaround because I'm using redux-form. This makes it much more complicated for this workaround.
I can confirm that all is working fine when I remove the multiline prop. I wrapped the whole application with KeyboardAvoidingView and my multiline TextInput is at the end of the form like in the author's example. I have no height applied to the TextInput which in a few cases can cause issues.
I am using react-native: 0.53.0

same problem here and still have no solution

I too facing the same issue. It works fine if multiline is set to false and not if set to true. My TextInput does not have any height. _Any solutions_??

A workaround for this is to make a reference to the parent scrollview and call its scrollTo method to the multilined TextInput coordinates whenever onFocus is called.

<KeyboardAvoidingView
   style={{ flex: 1 }}
   behavior="padding"
>
  <ScrollView ref={component => { this.myScrollView = component; }}>
    <TextInput 
       multiline
       onFocus={() => this.myScrollView.scrollTo({ x: 0, y: 750, animated: true })} // <- your coordinates here
    />
  </ScrollView>
</KeyboardAvoidingView>

same problem here only on IOS
textinput multiline has no effect on keyboardavoidingview

it is working fine on android

This has been quite frustrating for us as well.. Seems like the only consistent way to do this is by scrolling manually as @jasonchoibiz suggested.

It's an ugly workaround though! ☹️

there is news about this problem?

Still having this. Tried the suggested solutions.

same problem here

Same issue, here's a simple snack for anyone to play with: https://snack.expo.io/r1qpj0k5Q

Same issue, here's a simple snack for anyone to play with: https://snack.expo.io/r1qpj0k5Q

I've tried this and it works:

https://github.com/baijunjie/react-native-input-scroll-view

react-native version 0.56.1
same problem....

it is working;

<KeyboardAvoidingView behavior="padding" style={Platform.OS !== 'android' && { flex: 1 }}> 
  <ScrollView>
      ... 
      <TextInput ... />
      ...
  </ScrollView>
</KeyboardAvoidingView>

@recepkoseoglu It works on non-multiline Inputs. Try on multiline.

It's working if we use <View> to wrap the content instead of <ScrollView>, not ideal if you have many TextInputs though.

<KeyboardAvoidingView
    behavior={Platform.OS === "ios" ? "padding" : null} 
    style={{ flex: 1 }}
>
    <View style={{ flex: 1, justifyContent: "flex-end" }}>
        ...
        <TextInput multiline />
        <View style={{ flex: 1 }} /> // Use this to take up bottom spacing
    </View>
</KeyboardAvoidingView>

No fix yet?

RN 0.57... confirm same issue.

still same issue here. any solution?

I switched to "React-Native Keyboard Manager" and it works. Just install and your issues are solved

This is really causing a lot of problems. Any updates on this issue? I have tried using the libraries mentioned above but each of them come with sacrifices in user experience.

Same here. With multiline applied, KeyboardAvoidingView doesn't work.

Just hit this exact problem, and can confirm that KeyboardAvoidingView has no effect at all with any behaviour attribute option if the View contains a multiline TextInput

Super annoying. Any resolve here?

I tried to debug this today without much success. What I found out however is that _onKeyboardChange is triggered no matter if we have a multiline true or false. In both cases LayoutAnimation receives the same config:
https://github.com/facebook/react-native/blob/master/Libraries/Components/Keyboard/KeyboardAvoidingView.js#L107

I added an onAnimationDidEnd callback to LayoutAnimatijon and it has been called both for multiline true and false. In the case of false - the view was properly updated. In the case of multiline true as per the issue here - no layout change occurs.

So it seems that the KeyboardAvoidingView component behaves correctly both for multiline true or false, but LayoutAnimation fails to properly animate the position with multiline true.

KeyboardAvoidingView works fine with multiline TextInput because i am using them myself. Initially they would not work for me. To solve my problem i removed the height on the TextInput and set the behavior to "padding". The "flex: 1" you have on the KeyboardAvoidingView might be the problem i think or its the scrollview. But KeyboardAvoidingView definitely works fine with multiline.

Kinda feel weird to be the only person upvoting this one, but simply setting behavior to position worked for me!

If you want to KeyboardAvoidingView works fine with multiline TextInput,
please setting the scrollEnabled prop of TextInput to false.
It works for me.

If you want to KeyboardAvoidingView works fine with multiline TextInput,
please setting the scrollEnabled prop to false.
It works for me.

It works for me, too

@bob76828 Thanks for saving my time.

Does setting scrollEnabled={false} for KeyboardAvoidingView work inside a ScrollView? Refer to the OP for how it's used in such a case.

@peacechen You should use ScrollView inside KeyboardAvoidingView

<KeyboardAvoidingView>
  <ScrollView>
    <TextInput
      multiline={true}
      scrollEnabled={false} // to make keyboard avoiding works
    >
    </TextInput>
  </ScrollView>
</KeyboardAvoidingView>

Referring to my snippet posted above, there's no height on the TextInput, and KeyboardAvoidingView's behavior is set to padding.

Its style needs flex:1 to allow the ScrollView to take up the entire height.

Try the code with and without multiline set. It fails with multiline set, but the same code works for single line TextInputs.

^ This from @peacechen worked for me perfectly. I have scrollEnabled={true} & multiline={true}


_Code:_

<KeyboardAvoidingView
  behavior="padding"
  style={{flex: 1, flexDirection: "column"}}
>
  <View style={{flex: 1}}>
    <TextInput
        multiline={true}
        scrollEnabled={true}
        style={{flex:1}}
        // Other relevant props
      />
  </View>
<KeyboardAvoidingView />

scrollEnabled={false} // to make keyboard avoiding works

Yep, but if this option enabled with eg numberOfLines={3}, we have multiline textinput without scrolling which is not so good for forms with lots of other fields

@adteague the problem persists when you have a scrollview inside the KeyboardAvoidingView instead of a simple view

@gbalduzzi You don't add a ScrollView component. You make the TextInput multiple lines with scrolling enabled. See my code example above.

@adteague
A ScrollView is needed when the form is longer than the screen height. Multiple TextInputs live within the ScrollView. Does your approach solve that use case?

@peacechen No it would not. Try...

<KeyboardAvoidingView
  behavior="height"
  style={{flex: 1, flexDirection: "column"}}
>
  <ScrollView style={{flex: 1}}>
    <TextInput
        multiline={false}  // Default is false so you can remove this line
        scrollEnabled={false} // Default is false so you can remove this line
        style={{flex: 0}}
        // Other relevant props
      />

      <TextInput
        style={{flex: 0}}
        // Other relevant props
      />
  </ScrollView>
<KeyboardAvoidingView />

_(Not on my dev computer so sorry if it doesn't works)_

That's the same structure as the code snippet in the OP, which results in the multiline TextInput bug.

Edit: I haven't had the opportunity to test out your code snippet a few posts prior. It looks like yours works because it's not in a ScrollView.

@adteague My whole screen is wrapped by a KeyboardAvoidingView and, inside that, I have a Scrollview because the content is long and need to be scrolled.

At the end of the screen, I have a multiline textinput. On iOS, when the user tap on the text input it opens the keyboard without moving the input. If I set multiline={false}, it works perfectly.

The overall structure is the following:

<SafeAreaView style={{flex: 1}}>
  <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : null} style={{flex: 1}}>
    <ScrollView style={{flex: 1}} contentContainerStyle={{flexGrow: 1}}>
      // Some other stuff
      <TextInput style={{width: 200, height: 300}} multiline={true} scrollEnabled={false} />
    </ScrollView>
  </KeyboardAvoidingView>
</SafeAreaView>

@gbalduzzi try doing this way:

<KeyboardAvoidingView
      enabled
      behavior={ Platform.OS === 'ios' ? 'padding' : false }
      style={{ flex: 1}}
    >
      <ScrollView
        keyboardShouldPersistTaps={'always'}
        keyboardDismissMode={ Platform.OS === 'ios' ? 'interactive' : 'on-drag'}
      >
              <TextInput style={{width: 200, height: 300}} multiline={true} scrollEnabled={false} />
      </ScrollView>
</KeyboardAvoidingView>

Keyboard will not overlap multiline TextInput, so you can scroll and edit text. It will hide on swipe to top in interactive mode on iOS, but KeyboardAvoidingView will not scroll to multiline TextInput. Android should be ok.

If you want to KeyboardAvoidingView works fine with multiline TextInput,
please setting the scrollEnabled prop of TextInput to false.
It works for me.

This one worked for me! Thanks @bob76828

This is still an issue on 0.61.4

No fix yet? This issue is over two (2) years old now...
Edit: To make this a more helpful post: I've achieved what I need by using the react-native-keyboard-aware-scroll-view library and adding the scrollEnabled={false} prop to the TextInput.

@bob76828 Super thanks a lot man!!!

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

Not stale

Same issue, here's a simple snack for anyone to play with: https://snack.expo.io/r1qpj0k5Q

I've tried this and it works:

https://github.com/baijunjie/react-native-input-scroll-view

This Really worked for me. Thanks

still unfixed 😭

If anyone still is looking for a fix, you could try

<View style={{position:relative}}>
    <TextInput ref='multilineText' value={this.state.textValue} 
        onChangeText={(textValue) => this.setState({textValue})}/>
    <TextInput style={{ color:'transparent', position: 'absolute', width: '100%', height: '100%' }}
        placeholderTextColor='transparent'
        onFocus={() => setTimeout(() => this.refs.multilineText.focus(), 1000) } 
        value={this.state.textValue}
        onChangeText={(textValue) => this.setState({textValue})}
    />
</View>

Its a dirty hack but it does the job, what it does it when user clicks on the input it would be clicking on the single line and the keyboard will scroll to that line then after 1 seconds (you can adjust this, i just found 1 seconds to be good with our app) it would focus on the multiline. The value and onChangeText makes sure that when the user star typing they would have the same value except the single line has transparent text.

The downside is when a user clicked the multiple line, the cursor wont go to the location they clicked but it would go to the end.

Thanks, it works for me. I add caretHidden={true} for transparent TextInput to hide its cursor.

If anyone still is looking for a fix, you could try

<View style={{position:relative}}>
    <TextInput ref='multilineText' value={this.state.textValue} 
        onChangeText={(textValue) => this.setState({textValue})}/>
    <TextInput style={{ color:'transparent', position: 'absolute', width: '100%', height: '100%' }}
        placeholderTextColor='transparent'
        onFocus={() => setTimeout(() => this.refs.multilineText.focus(), 1000) } 
        value={this.state.textValue}
        onChangeText={(textValue) => this.setState({textValue})}
    />
</View>

Its a dirty hack but it does the job, what it does it when user clicks on the input it would be clicking on the single line and the keyboard will scroll to that line then after 1 seconds (you can adjust this, i just found 1 seconds to be good with our app) it would focus on the multiline. The value and onChangeText makes sure that when the user star typing they would have the same value except the single line has transparent text.

The downside is when a user clicked the multiple line, the cursor wont go to the location they clicked but it would go to the end.

From an automation perspective (using Appium) this works, you just need to make sure you put the testID/accessibilityLabel on the hidden input so that the actions are carried out against that instead of the visible one.

I had to reduce the timeout to 100ms instead of the full second as this was causing issues with the text entry as it would miss characters.

And if you use hideKeyboard us the 'tapOutside' strategy as the default one which uses return just creates a new line

textinput will hidden when input too many line on scroll view? it only android. i must allway add height for textinput
any solution for this bug ?

<ScrollView>
                    <View style={styles.container}>
                        <TextInput
                            onChangeText={(text) => {
                                this.setState({ moTa: text })
                            }}
                            value={this.state.moTa}
                            multiline={true}
                            scrollEnabled={false}
                            placeholder='Nhập thông tin mô tả'
                            style={styles.input}
                        />
                    </View>
                </ScrollView>

input: {                     
        ...(Platform.OS == 'android' ? { height: themes.screen.height - 400 } : {}),
        fontSize: 18,
        textAlignVertical: 'top', fontFamily: themes.fontFamily
    },
Was this page helpful?
0 / 5 - 0 ratings