react-native icon indicating copy to clipboard operation
react-native copied to clipboard

KeyboardAvoidingView mistakingly adds white space on screen bottom after being untangled

Open AndyYuanZhou opened this issue 3 years ago • 47 comments

[Description]

I wrapped a view as a footer inside KeyboardAvoidingView. It works properly before the keyboard has been untoggled. However, after the keyboard is untoggled, it adds a white space on the bottom of the screen. The height of the white space is as big as the keyboardVerticalOffset. This white space shouldn't exist.

React Native version:

System: OS: macOS 10.15.6 CPU: (8) x64 Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz Memory: 28.43 MB / 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 12.18.1 - /usr/local/bin/node Yarn: Not Found npm: 6.14.5 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.9.3 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: iOS 13.6, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2 Android SDK: Not Found IDEs: Android Studio: Not Found Xcode: 11.6/11E708 - /usr/bin/xcodebuild Languages: Java: 14.0.1 - /usr/bin/javac Python: 2.7.16 - /usr/bin/python npmPackages: @react-native-community/cli: Not Found react: ~16.11.0 => 16.11.0 react-native: https://github.com/expo/react-native/archive/sdk-38.0.1.tar.gz => 0.62.2 npmGlobalPackages: react-native: Not Found

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Use KeyboardAvoidingView, set keyboardVerticalOffset to any value
  2. Put a view inside KeyboardAvoidingView, set the position as absolute, and set the bottom to 0

Expected Results

The white space for keyboardVerticalOffset should only exist when the keyboard is toggled.

Snack, code example, screenshot, or link to a repository:

<KeyboardAvoidingView
      behavior={Platform.OS == "ios" ? "padding" : "height"}
      keyboardVerticalOffset={48}
      style={{width: "100%",
                   height: "100%",
                   position: "absolute",
                   zIndex: 1,}}
>
      <View
              style={{width: "100%",
                            position: "absolute",
                            height: 56,
                            bottom: 0,}}
      >
              <SubmitButton/>
       </View>
</KeyboardAvoidingView>

Artboard

AndyYuanZhou avatar Aug 11 '20 06:08 AndyYuanZhou

This looks similar to the sample for KeyboardAvoidingView. We're having trouble reproducing your snippet in a Snack. Mind helping refine this so we can get the repro? Have this so far, which more like the docs sample has the page content as well as the button inside the KeyboardAvoidingView.

chrisglein avatar Aug 11 '20 19:08 chrisglein

Thanks for responding and creating this snack. I just tried what you created. Try to add this prop keyboardVerticalOffset={100} to KeyboardAvoidingView and run it on iOS. You'll see how the button is pushed up after the keyboard gets toggled then dismissed. It works properly in Android.

AndyYuanZhou avatar Aug 12 '20 03:08 AndyYuanZhou

hey, guys, the same issue here, did you solve it?

TravellerOnTheRun avatar Dec 23 '20 03:12 TravellerOnTheRun

I'm having same trouble. In this example, when my screen is fit and no need to scroll it works perfectly. But when my screen has scrollable content, it adds white spaces in every input focusing under the <Button> component.

Note: With padding IOS platform works perfect.

Multiple input with behavior: "height"

Multiple Input with behavior: "padding"

Only one Input with behavior: "height"

My example Code:

<KeyboardAvoidingView
  style={{ flex: 1 }}
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
  keyboardVerticalOffset={Platform.OS === 'ios' ? (isIphoneX() ? 88 : 64) : StatusBar.currentHeight + 50} // 50 is Button height
  enabled>
   <ScrollView contentContainerStyle={{ flexGrow: 1 }} keyboardShouldPersistTaps={'handled'}>
     <Input />
    </ScrollView>
    <Button
      onPress={() => {}}
      style={{ justifyContent: 'flex-end' }}
      text={"Confirm"}
    />
</KeyboardAvoidingView>

I need a help for general solution. 🚀

keremcubuk avatar Jan 26 '21 09:01 keremcubuk

@keremcubuk Did you find a solution?

kostiantyn-solianyk avatar Apr 05 '21 12:04 kostiantyn-solianyk

Here's my solution, I can't promise it will work for everyone and in all situations but it's working where it needs to in my current project, it involves replacing 'flex: 1' with the window height minus the status bar height and setting the behavior to position. Hope it helps.

<KeyboardAvoidingView
        behavior="position"
        style={{
          height: Platform.OS === 'android' ? Dimensions.get('window').height - StatusBar.currentHeight : '100%',
          alignItems: 'center',
        }}
      >
....
</KeyboardAvoidingView>

F3WS avatar Aug 11 '21 15:08 F3WS

Thanks F3WS, but that solution not working for me. Anyone had any success using different workaround?

tomo10 avatar Oct 26 '21 10:10 tomo10

any solution?

Naua-stack avatar Jan 17 '22 16:01 Naua-stack

facing the same problem, did anyone find a solution?

annaostapenko22 avatar Jan 19 '22 10:01 annaostapenko22

Any solution?

talaviad5 avatar Jan 26 '22 17:01 talaviad5

In my case I added behavior={isAndroid ? 'height' : 'position'} and also needed to add different keyboardVerticalOffset for android, iPhoneX and normal iPhone

const calculateKeyboardVerticalOffset = () => {
  if (isAndroid) return StatusBar.currentHeight;
  if (isIphoneX()) return -100;
  return 0;
};

    <KeyboardAvoidingView
      behavior={isAndroid ? 'height' : 'position'}
      enabled
      keyboardVerticalOffset={calculateKeyboardVerticalOffset()}
    >

Hope this helps @talaviad5

annaostapenko22 avatar Jan 26 '22 17:01 annaostapenko22

@annaostapenko22 still doesn't work, but thanks

talaviad5 avatar Jan 26 '22 17:01 talaviad5

i fixed it too with

const additionHeight=Platform.OS=='ios'?0:StatusBar.currentHeight;

<KeyboardAvoidingView behavior={(Platform.OS === 'ios') ? "margin" :'padding'} contentContainerStyle={{ flex: 1 }} keyboardVerticalOffset={Platform.select({ios: 2, android: -additionHeight})}

Digiraf avatar May 13 '22 03:05 Digiraf

behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={-900}

this will fix your issue

sajjad-yousaf avatar Jun 07 '22 05:06 sajjad-yousaf

still have the issue, any solution ?

anckaertv avatar Aug 09 '22 15:08 anckaertv

Still not resolved.

nmateo avatar Nov 11 '22 15:11 nmateo

import {
KeyboardAvoidingView,SafeAreaView,TouchableWithoutFeedback,....
} from 'react-native';
<KeyboardAvoidingView
                style={{ flex: 1 }}
                behavior={(Platform.OS === 'ios') ? "padding" : null}
                keyboardVerticalOffset={Platform.select({ios: 80, android: 500})} enabled>
                <SafeAreaView style={styles.container}>
                    <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
                      <View style={{flex:1,padding:20}}>
                <TextInput maxLength={50} value={this.getVal('name')} onChangeText={(txt)=>{
                  ....
                }}

                     </View>
                    </TouchableWithoutFeedback>
                </SafeAreaView>

            </KeyboardAvoidingView>

Edit your AndroidManifest.xml

 <activity
                android:name=".MainActivity"
                android:label="@string/app_name"
                android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
                android:launchMode="singleTask"
            android:screenOrientation="landscape"
                android:windowSoftInputMode="adjustResize"
            android:exported="true"
            >

this will do the job

Digiraf avatar Nov 11 '22 16:11 Digiraf

I had same issue and my style for KAV was:

flex: 1

Inner ScrollView had flex: 1 too. Just changing KAV style to flexGrow: 1 fixed the issue.

arthwood avatar Dec 19 '22 09:12 arthwood

i replace my keyboardavoidingview with

https://github.com/APSL/react-native-keyboard-aware-scroll-view

and solved.

sLurPPPeRsTaR avatar Dec 26 '22 06:12 sLurPPPeRsTaR

I'm facing the same issue.

mmkhmk avatar Jan 20 '23 10:01 mmkhmk

I'm facing the same issue only on iOS. It adds a lot of whitespace at the bottom of a ScrollView and every time I blur and focus on the input again, it adds even more whitespace.

I think this is a major bug that needs to be addressed. The whitespace needs to be removed after the keyboard hides.

Rc85 avatar Feb 01 '23 03:02 Rc85

Try to set the behavior on iOS as "height"

abumubarak-dev avatar Feb 11 '23 07:02 abumubarak-dev

@abumubarak-dev height not working as excepted also

sergeushenecz avatar Feb 12 '23 13:02 sergeushenecz

Who know workaround? Because i have problem only when keyboard open and then hide. Empty space equal keyboardVerticalOffset not removed

sergeushenecz avatar Feb 12 '23 13:02 sergeushenecz

running into this issue too. Appears in sample code too

import React from 'react';
import {
  View,
  KeyboardAvoidingView,
  TextInput,
  StyleSheet,
  Text,
  Platform,
  TouchableWithoutFeedback,
  Button,
  Keyboard,
} from 'react-native';

const KeyboardAvoidingComponent = () => {
  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={500}
      style={styles.container}>
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <View style={styles.inner}>
          <Text style={styles.header}>Header</Text>
          <TextInput placeholder="Username" style={styles.textInput} />
          <View style={styles.btnContainer}>
            <Button title="Submit" onPress={() => null} />
          </View>
        </View>
      </TouchableWithoutFeedback>
    </KeyboardAvoidingView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  inner: {
    padding: 24,
    flex: 1,
    justifyContent: 'space-around',
  },
  header: {
    fontSize: 36,
    marginBottom: 48,
  },
  textInput: {
    height: 40,
    borderColor: '#000000',
    borderBottomWidth: 1,
    marginBottom: 36,
  },
  btnContainer: {
    backgroundColor: 'white',
    marginTop: 12,
  },
});

export default KeyboardAvoidingComponent;

JosephLu2022 avatar Mar 01 '23 21:03 JosephLu2022

I added in app.json

"android": { "softwareKeyboardLayoutMode": "pan" }

It solved

gfmachad avatar Apr 03 '23 21:04 gfmachad

I frequently encounter this bug and am forced to use react-native-keyboard-aware-scroll-view, but it doesn't work in all situations (like if I want to nest virtualized lists). It's hard to believe this core component is still so buggy.

Potentially related, I frequently run into keyboard dismissal bugs like this in apps from the iOS App Store on my iPhone 12 mini (including big names like Discord, which is known to be mostly built in React Native). Super frustrating stuff.

taylorkangbeck avatar Apr 12 '23 00:04 taylorkangbeck

I did a little more digging. While debugging I noticed that on keyboard close, the event received from iOS (keyboardWillChangeFrame) incorrectly returns (0, 0) as the new coordinates of the keyboard. The correct value (if using the same coordinate system as RN) should be (0, screen height). As a result, this throws off the bottom height calculation done here, resulting in the view's content being pushed up significantly.

It's possible that this would be fixed by changing RN's native bridge code to instead use UIKeyboardLayoutGuide as recommended in Apple's docs.

I don't know if I'll have time to post a PR for that myself, but it would be awesome if someone could try that out.

taylorkangbeck avatar Apr 12 '23 18:04 taylorkangbeck

I had same issue and my style for KAV was:

flex: 1

Inner ScrollView had flex: 1 too. Just changing KAV style to flexGrow: 1 fixed the issue.

This fixed it for me, and it's so much nicer than adding some extra library 🔥 Thanks!

image

Gogoro avatar May 07 '23 18:05 Gogoro

I faced a similar issue and I'll share my solution here in case this helps anyone.

Context

Note: this only happened in iOS (not Android).

In my case, when the keyboard was visible and I clicked a CTA to go to the next screen, I would get the extra white space (equal to the KeyboardAvoidingView's keyboardVerticalOffset) at the bottom of the next screen.

Clicking on the CTA dismissed the keyboard, however while the keyboard was still being dismissed the navigation to the next page started (calling push from solito/router). Basically, the user arrived at the next screen while the keyboard was not fully dismissed. Therefore it seems the KeyboardAvoidingView kept applying the keyboardVerticalOffset even though the keyboard was eventually dismissed.

In fact, waiting for the keyboard to be dismissed before navigating to the next screen solved the issue. As did wrapping the call to router.push in a setTimeout (with even just a 50ms delay).

Solution

The main idea is to disable the KeyboardAvoidingView as soon as we know the keyboard will be hidden:

export const MyKeyboardAvoidingView = ({ behavior, children }: Props) => {
	const headerHeight = useHeaderHeight()

	const [enabled, setEnabled] = useState(false)

	useEffect(() => {
		const showSubscription = Keyboard.addListener('keyboardWillShow', () => {
			setEnabled(true)
		})

		const hideSubscription = Keyboard.addListener('keyboardWillHide', () => {
			setEnabled(false)
		})

		return () => {
			showSubscription.remove()
			hideSubscription.remove()
		}
	}, [])

	return (
		<KeyboardAvoidingView
			behavior={behavior}
			enabled={enabled}
			keyboardVerticalOffset={headerHeight}
		>
			{children}
		</KeyboardAvoidingView>
	)
}

alpalla avatar May 29 '23 14:05 alpalla