Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KeyboardAvoidingView sibling to KeyboardAwareScrollView creates empty space at the bottom of the KeyboardAwareScrollView #451

Closed
itsramiel opened this issue May 21, 2024 · 4 comments
Assignees
Labels
question You wanted to clarify something about the usage of the library or have a question about something

Comments

@itsramiel
Copy link

Describe the bug
When KeyboardAvoidingView is a sibling to KeyboardAwareScrollView then:

  1. KeyboardAwareScrollView has extra space at the bottom of it (main issue)
  2. The textinput inside KeyboardAwareScrollView is not correctly positioned from the first try, take a bit to adjust

Code snippet
What I am trying to achieve is a screen with a list that contains arbitrary views and an input and a footer button. I would like when the user click on the input for it to be focused/visible while also having the button visible to allow the user to take action(done button in footer) without needing to close the keyboard first

import {Button, SafeAreaView, TextInput, View} from 'react-native';
import {
  KeyboardAvoidingView,
  KeyboardAwareScrollView,
  KeyboardProvider,
} from 'react-native-keyboard-controller';

function App() {
  return (
    <KeyboardProvider>
      <Screen />
    </KeyboardProvider>
  );
}

function Screen() {
  return (
    <SafeAreaView style={{flex: 1}}>
      <KeyboardAwareScrollView contentContainerStyle={{gap: 8}}>
        {Array(7)
          .fill(0)
          .map((_, i) => (
            <View key={i} style={{height: 80, backgroundColor: 'blue'}} />
          ))}
        <TextInput style={{padding: 16, backgroundColor: 'red'}} />
      </KeyboardAwareScrollView>
      <KeyboardAvoidingView behavior="padding">
        <Button title="done" />
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}

export default App;

Repo for reproducing
https://github.com/itsramiel/keyboard-controller-repro

To Reproduce
Steps to reproduce the behavior:

  1. clone the repo
  2. click on the red text input, then scroll down

Expected behavior
I expect not to have extra space on the bottom

ScreenRecording

Simulator.Screen.Recording.-.iPhone.15.-.2024-05-21.at.12.46.57.mp4

Smartphone (please complete the following information):

  • Desktop OS: MacOS 14.3.1
  • Device: Iphone 15 pro simulator
  • OS: iOS 17.2
  • RN version: 0.74
  • RN architecture: old
  • JS engine: Hermes
  • Library version: 1.2.1

Additional context
I am not sure why the text input also lags a bit behind and is not correctly position from the first time.

@kirillzyusko
Copy link
Owner

@itsramiel shouldn't you use KeyboardStickyView instead of KeyboardAvoidingView in this example?

From a quick look KeybiardStickyView should fit better in this layout. I have an example app for a reference: https://github.com/kirillzyusko/react-native-keyboard-controller/blob/main/example/src/screens/Examples/AwareScrollViewStickyFooter/index.tsx

@itsramiel
Copy link
Author

I don't think KeyboardStickyView is better here since it overlays other views and in your example you are creating a state variable for the height of the footer and using that for the offset of the KeyboardAwareScrollView. I'd rather it be more declarative and. fail proof.

Actually in my case I want the whole screen to be avoiding the keyboard along with the scrollview to keep the focused input visible so I can change my returned jsx to:

  return (
    <KeyboardAvoidingView style={{ flex: 1 }} behavior="padding">
      <KeyboardAwareScrollView
        style={{ flex: 1 }}
        contentContainerStyle={{ gap: 8 }}>
        {Array(7)
          .fill(0)
          .map((_, i) => (
            <View key={i} style={{ height: 80, backgroundColor: 'blue' }} />
          ))}
        <TextInput style={{ padding: 16, backgroundColor: 'red' }} />
      </KeyboardAwareScrollView>
      <Button title="done" />
    </KeyboardAvoidingView>
  );

but I still get the same behavior:

Simulator.Screen.Recording.-.iPhone.15.-.2024-05-21.at.18.29.54.mp4

changes are on branch alt1

One thing that I tried by mistake and kinda works is to wrap the whole screen with KeyboardAvoidingView like above but also to replace KeyboardAvoidingScrollView with normal scrollview then the extra space is no longer there but it takes a while for the focused input to come into view. I dont really understand why this kinda works. I really tried that by mistake and can the input be focused immediately without a delay?

code:

  return (
    <KeyboardAvoidingView style={{flex: 1}} behavior="padding">
      <ScrollView style={{flex: 1}} contentContainerStyle={{gap: 8}}>
        {Array(7)
          .fill(0)
          .map((_, i) => (
            <View key={i} style={{height: 80, backgroundColor: 'blue'}} />
          ))}
        <TextInput style={{padding: 16, backgroundColor: 'red'}} />
      </ScrollView>
      <Button title="done" />
    </KeyboardAvoidingView>
  );

video:

Simulator.Screen.Recording.-.iPhone.15.-.2024-05-21.at.18.35.23.mp4

changes are on branch alt2

Looking forward to you reply!

@kirillzyusko
Copy link
Owner

Hey @itsramiel

Sorry for a long response. Basically when you combine two view (i. e. KeyboardAvoidingView and KeyboardAwareScrollView) they both will add its own padding - and it eventually will lead to double space -> this is how it's designed to work.

Regarding wrapping ScrollView into KeyboardAvoidingView - it may work, but honestly I don't know why scroll happens automatically. Maybe some code calls scrollResponderScrollNativeHandleToKeyboard (from ScrollView) - need to investigate it. But since it's handled mostly on RN core - you will not get frame in frame precision (i. e. the delay will be always present).

My suggestion would be to stick to example app and use KeyboardAwareScrollView + KeyboardStickyView.

and in your example you are creating a state variable for the height of the footer and using that for the offset of the KeyboardAwareScrollView. I'd rather it be more declarative

You always can create your own component, like:

const KeyboardAwareScrollViewWithFooter = ({ footer, children }) => {
  const [footerOffset, setFooterOffset] = useState(0);
  
  const onLayout = (e) => {
    setFooterOffset(e.nativeEvent.layout.height);
  };
  
  return (
    <>
      <KeyboardAwareScrollView>
        {children}
      </KeyboardAwareScrollView>
      <View onLayout={onLayout}>
        {footer}
      </View>
    </>
  );
}

And use this component in declarative way. Let me know if I'm missing anything 🙏

@kirillzyusko kirillzyusko added the question You wanted to clarify something about the usage of the library or have a question about something label May 28, 2024
@itsramiel
Copy link
Author

I would have preferred to have a solution without manually measuring the height and applying a bottom padding but if that is the state then alright. Thanks for the response. I will be closing the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question You wanted to clarify something about the usage of the library or have a question about something
Projects
None yet
Development

No branches or pull requests

2 participants