mateusz1913 / react-native-avoid-softinput

Native solution for common React Native problem of focused views being covered by soft input view.
https://mateusz1913.github.io/react-native-avoid-softinput/
MIT License
700 stars 18 forks source link

AvoidSoftInputView breaks with screen swipe #54

Closed nucleartux closed 2 years ago

nucleartux commented 2 years ago

Environment

Library version: 2.4.1 OS version: iPhone 13, iOS 15.4 "@react-navigation/native": "^6.0.8", "@react-navigation/native-stack": "^6.6.1",

Affected platforms

Current behavior

https://user-images.githubusercontent.com/199706/161307457-5d118360-c530-479a-b52e-c69cd62ef98b.mp4

Expected behavior

Keyboard avoid view

Reproduction

  1. Open screen
  2. Focus keyboard
  3. Start swiping back
  4. Cancel swipe Simplified code:
    
    <View style={styles.container}>
      <AvoidSoftInputView style={styles.container} avoidOffset={16}>
        <ImageBackground
          source={require("./bg.png")}
          imageStyle={styles.bgImage}
          resizeMode="repeat"
          style={styles.bg}
        >
          <FlatList />
        </ImageBackground>
        <View style={styles.bottom}>
          <TextInput />
        </View>
      </AvoidSoftInputView>
      <SafeAreaView style={styles.footer} edges={["bottom"]} />
    </View>

const styles = StyleSheet.create({ container: { flex: 1, }, ... })



And I have related question: now, when I swipe screen keyboard still present but keyboard avoid view smoothly disappears (and content scrolls down). How I can prevent this behavior?
a-eid commented 2 years ago

I think you should not use AvoidSoftInputView, could you try setEnable(true) when the screen is active & setEanble(false) when it's not ?

nucleartux commented 2 years ago

I deleted AvoidSoftInputView and added this code:

  const onFocusEffect = useCallback(() => {
    AvoidSoftInput.setEnabled(true); 
    return () => {
      AvoidSoftInput.setEnabled(false);
    };
  }, []);

  useFocusEffect(onFocusEffect);

Now it completely hides navigation header. How I can fix that?

https://user-images.githubusercontent.com/199706/161310942-b250944a-9027-4b43-b47a-39382e8f8b9a.mp4

And anyway bug still present. Even if I try to disable on transition start:

  useEffect(() => {
    const unsubscribe = navigation.addListener("transitionStart", () => {
      AvoidSoftInput.setEnabled(false);
    });

    return unsubscribe;
  }, [navigation]);
mateusz1913 commented 2 years ago

Hi @nucleartux

And I have related question: now, when I swipe screen keyboard still present but keyboard avoid view smoothly disappears (and content scrolls down). How I can prevent this behavior?

That's default iOS behavior, keyboard is still fully visible, even though app receives events from UIResponder.keyboardWillChangeFrameNotification:

I don't know if it can be prevented, you can try with writing your own logic for applying padding to your container based on useSoftInputShown & useSoftInputHidden hooks, but I don't guarantee, that it will make a satisfying result.

mateusz1913 commented 2 years ago

@nucleartux issue is fixed and available in version 2.4.2 🎉

nucleartux commented 2 years ago

@mateusz1913 hello I updated library and added screenOptions. Now screen (avoid soft input part) moves together with keyboard but still breaks on swipe cancel. So it's expected behaviour, right?

https://user-images.githubusercontent.com/199706/161418102-c1b9b20a-7536-409a-8383-a60f0fd5ab94.mov

mateusz1913 commented 2 years ago

On the provided video, after you cancel swipe, can you scroll to the very bottom (so the input and all messages are visible as it was before the swipe)?

If not, please create reproduction repo and link it in issue's description, so I will be able to investigate and reopen this issue

nucleartux commented 2 years ago

It's not possible.

https://user-images.githubusercontent.com/199706/161418853-aa17d141-9230-4f56-9806-4e8e8c9e4716.mov

I will try to make repro demo.

nucleartux commented 2 years ago

I'm in process of repro but found another bug(?). Can you help me investigate it? https://github.com/nucleartux/TestProject

https://user-images.githubusercontent.com/199706/161420146-a12c1bed-4efe-4b3a-9c73-9053dfcb6603.mp4

nucleartux commented 2 years ago

Ok I get it. If you add avoidOffset={16} to my demo it will collapse keyboard after swipe cancel. Without avoidOffset it adds height every time swipe canceled.

So maybe it's two separate bugs, i'm not sure.

mateusz1913 commented 2 years ago

I downloaded your repo and can't reproduce behavior from video

https://user-images.githubusercontent.com/25980166/161428364-cf03d614-122b-4ac6-b2c6-cc0ba80bb928.mp4

I added nice Swift package ShowTime, so you will see where I make touches

nucleartux commented 2 years ago

How it is even possible? I tried delete node_modules and pods and reinstall everything again but bug still exists.

mateusz1913 commented 2 years ago

Which simulator, do you use?

nucleartux commented 2 years ago

Simulator. iPhone 13, iOS 15.4

mateusz1913 commented 2 years ago

Weird. I will try on real device

nucleartux commented 2 years ago

I tried on real device (iPhone X, latest iOS) - same bug.

mateusz1913 commented 2 years ago

Ok, I reproduced on real device. Will investigate it and probably create separate ticket.

In the meantime, please prepare that chat repro and I will reopen this ticket

mateusz1913 commented 2 years ago

I will track second one here: https://github.com/mateusz1913/react-native-avoid-softinput/issues/59

nucleartux commented 2 years ago

I made separate branch for this issue https://github.com/nucleartux/TestProject/tree/issue-54

https://user-images.githubusercontent.com/199706/161431126-a6ced7ce-8e27-434b-a37d-5252dba4ad9f.mp4

mateusz1913 commented 2 years ago

@nucleartux Can you try following patch?

diff --git a/node_modules/react-native-avoid-softinput/ios/AvoidSoftInputManager.swift b/node_modules/react-native-avoid-softinput/ios/AvoidSoftInputManager.swift
index 3f30b5a..685dd3f 100644
--- a/node_modules/react-native-avoid-softinput/ios/AvoidSoftInputManager.swift
+++ b/node_modules/react-native-avoid-softinput/ios/AvoidSoftInputManager.swift
@@ -34,6 +34,8 @@ class AvoidSoftInputManager: NSObject {
     private var showDelay: Double = SHOW_ANIMATION_DELAY_IN_SECONDS
     private var showDuration: Double = SHOW_ANIMATION_DURATION_IN_SECONDS
     private var softInputVisible: Bool = false
+    private var wasAddOffsetInRootViewAborted = false
+    private var wasAddOffsetInScrollViewAborted = false

     func setAvoidOffset(_ offset: NSNumber) {
         avoidOffset = CGFloat(offset.floatValue)
@@ -157,7 +159,7 @@ class AvoidSoftInputManager: NSObject {

     private func decreaseOffsetInRootView(from: CGFloat, to: CGFloat, rootView: UIView) {
         let addedOffset = to - from
-        let newBottomOffset = isShowAnimationRunning ? bottomOffset : bottomOffset + addedOffset
+        let newBottomOffset = isShowAnimationRunning || wasAddOffsetInRootViewAborted ? bottomOffset : bottomOffset + addedOffset

         if newBottomOffset < 0 {
             return
@@ -175,7 +177,7 @@ class AvoidSoftInputManager: NSObject {

     private func increaseOffsetInRootView(from: CGFloat, to: CGFloat, rootView: UIView) {
         let addedOffset = to - from
-        let newBottomOffset = isHideAnimationRunning ? bottomOffset : bottomOffset + addedOffset
+        let newBottomOffset = isHideAnimationRunning || wasAddOffsetInRootViewAborted ? bottomOffset : bottomOffset + addedOffset

         if newBottomOffset < 0 {
             return
@@ -211,6 +213,7 @@ class AvoidSoftInputManager: NSObject {

     private func addOffsetInRootView(_ offset: CGFloat, firstResponder: UIView, rootView: UIView) {
         guard let firstResponderPosition = firstResponder.superview?.convert(firstResponder.frame.origin, to: nil) else {
+            wasAddOffsetInRootViewAborted = true
             return
         }

@@ -221,13 +224,15 @@ class AvoidSoftInputManager: NSObject {

         let firstResponderDistanceToBottom = UIScreen.main.bounds.size.height - (firstResponderPosition.y + firstResponder.frame.height) - bottomSafeInset

-        let newOffset = max(offset - firstResponderDistanceToBottom, 0) + avoidOffset
+        let newOffset = max(offset - firstResponderDistanceToBottom, 0)

         if (newOffset <= 0) {
+            wasAddOffsetInRootViewAborted = true
             return
         }

-        bottomOffset = newOffset
+        wasAddOffsetInRootViewAborted = false
+        bottomOffset = newOffset + avoidOffset

         beginShowAnimation(initialOffset: 0, addedOffset: bottomOffset)
         UIView.animate(withDuration: showDuration, delay: showDelay, options: [.beginFromCurrentState, easingOption]) {
@@ -265,7 +270,7 @@ class AvoidSoftInputManager: NSObject {

     private func decreaseOffsetInScrollView(from: CGFloat, to: CGFloat, firstResponder: UIView, scrollView: UIScrollView, rootView: UIView) {
         let addedOffset = to - from
-        let newBottomOffset = isShowAnimationRunning ? bottomOffset : bottomOffset + addedOffset
+        let newBottomOffset = isShowAnimationRunning || wasAddOffsetInScrollViewAborted ? bottomOffset : bottomOffset + addedOffset
         let scrollToOffset = getScrollToOffset(softInputHeight: to, firstResponder: firstResponder, scrollView: scrollView, rootView: rootView)

         if newBottomOffset < 0 {
@@ -291,7 +296,7 @@ class AvoidSoftInputManager: NSObject {

     private func increaseOffsetInScrollView(from: CGFloat, to: CGFloat, firstResponder: UIView, scrollView: UIScrollView, rootView: UIView) {
         let addedOffset = to - from
-        let newBottomOffset = isHideAnimationRunning ? bottomOffset : bottomOffset + addedOffset
+        let newBottomOffset = isHideAnimationRunning || wasAddOffsetInScrollViewAborted ? bottomOffset : bottomOffset + addedOffset
         let scrollToOffset = getScrollToOffset(softInputHeight: to, firstResponder: firstResponder, scrollView: scrollView, rootView: rootView)

         if newBottomOffset < 0 {
@@ -344,6 +349,7 @@ class AvoidSoftInputManager: NSObject {
         }

         guard let scrollViewPosition = scrollView.superview?.convert(scrollView.frame.origin, to: nil) else {
+            wasAddOffsetInScrollViewAborted = true
             return
         }

@@ -351,13 +357,15 @@ class AvoidSoftInputManager: NSObject {

         let scrollToOffset = getScrollToOffset(softInputHeight: offset, firstResponder: firstResponder, scrollView: scrollView, rootView: rootView)

-        let newOffset = max(offset - scrollViewDistanceToBottom, 0) + avoidOffset
+        let newOffset = max(offset - scrollViewDistanceToBottom, 0)

         if newOffset <= 0 {
+            wasAddOffsetInScrollViewAborted = true
             return
         }

-        bottomOffset = newOffset
+        wasAddOffsetInScrollViewAborted = false
+        bottomOffset = newOffset + avoidOffset

         if !softInputVisible {
             // Save original scroll insets

I think it should resolve both issues

nucleartux commented 2 years ago

Tested on repro and real project, works fine! You saved my life 🕺

nucleartux commented 2 years ago

But one question. It works fine with fullScreenGestureEnabled enabled but without it on swipe flat list goes under keyboard (video in very first post). Can I somehow workaround it and with disabled fullScreenGestureEnabled?

It's just question, issue is solved anyway.

mateusz1913 commented 2 years ago

I'll check that, probably it shouldn't work like that

mateusz1913 commented 2 years ago

Ah, ok, I misunderstood your question. So I guess that fullScreenGestureEnabled option is making keyboard interactive when dismissing it

mateusz1913 commented 2 years ago

Fix is available in v2.4.3