react-native-avoid-softinput: AvoidSoftInputView breaks with screen swipe

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

  • Android
  • iOS

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?

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 24 (23 by maintainers)

Commits related to this issue

Most upvoted comments

@nucleartux issue is fixed and available in version 2.4.2 🎉

Fix is available in v2.4.3

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

@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

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

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

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:

  • first when swipe is started - from: “keyboardCoordinatesWhenItIsVisible”, to: “keyboardCoordinatesWhenItIsDismissed”
  • second when swipe is aborted - from: “keyboardCoordinatesWhenItIsDismissed”, to: “keyboardCoordinatesWhenItIsVisible”).

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.