facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
119.23k stars 24.34k forks source link

KeyboardAvoidingView has no effect on multiline TextInput #16826

Open peacechen opened 7 years ago

peacechen commented 7 years ago

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>
        );
    }
vivalaakam commented 7 years ago

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

vivalaakam commented 7 years ago

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

jibberilins commented 6 years ago

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.

peacechen commented 6 years ago

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.

hoscarcito commented 6 years ago

Same problem here!

fadlykayo commented 6 years ago

@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?

sherxia92 commented 6 years ago

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

monmonja commented 6 years ago

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.

peacechen commented 6 years ago

@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.

kfroemming-cb commented 6 years ago

Has anyone found a good solution for this?

chmielot commented 6 years ago

@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

Hsuer commented 6 years ago

same problem here and still have no solution

sndkd commented 6 years ago

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??

jasonchoibiz commented 6 years ago

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>
lucasboleli commented 6 years ago

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

it is working fine on android

amsul commented 6 years ago

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! ☹️

robertobrogi commented 6 years ago

there is news about this problem?

luco commented 6 years ago

Still having this. Tried the suggested solutions.

mitevdev commented 6 years ago

same problem here

Jmedders commented 6 years ago

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

mitevdev commented 6 years ago

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

linoleum00 commented 6 years ago

react-native version 0.56.1 same problem....

recepkoseoglu commented 6 years ago

it is working;

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

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

mk-nickyang commented 6 years ago

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>
luco commented 5 years ago

No fix yet?

ankhanguit commented 5 years ago

RN 0.57... confirm same issue.

boraerbasoglu commented 5 years ago

still same issue here. any solution?

matteocollina commented 5 years ago

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

kfroemming-cb commented 5 years ago

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.

noll-fyra commented 5 years ago

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

codewithfeeling commented 5 years ago

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

franzwarning commented 5 years ago

Super annoying. Any resolve here?

compojoom commented 5 years ago

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.

tuzmusic commented 5 years ago

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!

bob76828 commented 5 years ago

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

dy93 commented 5 years ago

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

lalayueh commented 5 years ago

@bob76828 Thanks for saving my time.

peacechen commented 5 years ago

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

dy93 commented 5 years ago

@peacechen You should use ScrollView inside KeyboardAvoidingView

<KeyboardAvoidingView>
  <ScrollView>
    <TextInput
      multiline={true}
      scrollEnabled={false} // to make keyboard avoiding works
    >
    </TextInput>
  </ScrollView>
</KeyboardAvoidingView>
adteague commented 5 years ago

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 />
artshevtsov commented 5 years ago

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

gbalduzzi commented 5 years ago

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

adteague commented 5 years ago

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

peacechen commented 5 years ago

@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?

adteague commented 5 years ago

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

peacechen commented 5 years ago

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.

gbalduzzi commented 5 years ago

@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>
artshevtsov commented 5 years ago

@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.

nicoferreyra commented 5 years ago

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