From 2d9933e616c3efe57ed0ca141277638182a69d9c Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Wed, 30 Oct 2024 12:01:10 -0700 Subject: [PATCH] automaticallyAdjustKeyboardInsets not shifting scrollview content (#46732) Summary: Fixes https://github.com/facebook/react-native/issues/46595 . It seems https://github.com/facebook/react-native/issues/37766 broke the `automaticallyAdjustKeyboardInsets` when input accessory view become first responder. ## Changelog: [IOS] [FIXED] - automaticallyAdjustKeyboardInsets not shifting scrollview content Pull Request resolved: https://github.com/facebook/react-native/pull/46732 Test Plan: Repro please see in #https://github.com/facebook/react-native/issues/46595 . Reviewed By: cipolleschi Differential Revision: D65072478 Pulled By: javache fbshipit-source-id: 7d5d7566438d4bb0e1d50074a953b18866e324d3 --- .../Text/TextInput/RCTBaseTextInputView.mm | 1 + .../ScrollView/RCTScrollViewComponentView.h | 3 ++ .../ScrollView/RCTScrollViewComponentView.mm | 30 +++++++++++-------- .../TextInput/RCTTextInputComponentView.mm | 1 + .../React/Views/ScrollView/RCTScrollView.h | 2 ++ .../React/Views/ScrollView/RCTScrollView.m | 6 ++++ 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm index 349e53cef6f..b9c699b8daa 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm @@ -36,6 +36,7 @@ static NSSet *returnKeyTypesSet; { if (![self isDescendantOfView:scrollView]) { // View is outside scroll view + scrollView.firstResponderViewOutsideScrollView = self.backedTextInputView; return; } diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h index 12f4209f63a..99b7240b83d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h @@ -38,6 +38,9 @@ NS_ASSUME_NONNULL_BEGIN /** Focus area of newly-activated text input relative to the window to compare against UIKeyboardFrameBegin/End */ @property (nonatomic, assign) CGRect firstResponderFocus; +/** newly-activated text input outside of the scroll view */ +@property (nonatomic, weak) UIView *firstResponderViewOutsideScrollView; + /* * Returns the subview of the scroll view that the component uses to mount all subcomponents into. That's useful to * separate component views from auxiliary views to be able to reliably implement pull-to-refresh- and RTL-related diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 6e198a0bfa2..bb63afb3c3d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -182,6 +182,7 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt UIViewAnimationCurve curve = (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue]; CGRect keyboardEndFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + CGRect keyboardBeginFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; CGPoint absoluteViewOrigin = [self convertPoint:self.bounds.origin toView:nil]; CGFloat scrollViewLowerY = isInverted ? absoluteViewOrigin.y : absoluteViewOrigin.y + self.bounds.size.height; @@ -203,21 +204,24 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt from:self forEvent:nil]) { if (CGRectEqualToRect(_firstResponderFocus, CGRectNull)) { - // Text input view is outside of the scroll view. - return; - } + UIView *inputAccessoryView = _firstResponderViewOutsideScrollView.inputAccessoryView; + if (inputAccessoryView) { + // Text input view is within the inputAccessoryView. + contentDiff = keyboardEndFrame.origin.y - keyboardBeginFrame.origin.y; + } + } else { + CGRect viewIntersection = CGRectIntersection(self.firstResponderFocus, keyboardEndFrame); - CGRect viewIntersection = CGRectIntersection(self.firstResponderFocus, keyboardEndFrame); + if (CGRectIsNull(viewIntersection)) { + return; + } - if (CGRectIsNull(viewIntersection)) { - return; - } - - // Inner text field focused - CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus); - if (focusEnd > keyboardEndFrame.origin.y) { - // Text field active region is below visible area with keyboard - update diff to bring into view - contentDiff = keyboardEndFrame.origin.y - focusEnd; + // Inner text field focused + CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus); + if (focusEnd > keyboardEndFrame.origin.y) { + // Text field active region is below visible area with keyboard - update diff to bring into view + contentDiff = keyboardEndFrame.origin.y - focusEnd; + } } } diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 00cf7563d35..01a066d3c51 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -126,6 +126,7 @@ static NSSet *returnKeyTypesSet; { if (![self isDescendantOfView:scrollView.scrollView] || !_backedTextInputView.isFirstResponder) { // View is outside scroll view or it's not a first responder. + scrollView.firstResponderViewOutsideScrollView = _backedTextInputView; return; } diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.h b/packages/react-native/React/Views/ScrollView/RCTScrollView.h index d57793b65d9..10ef46a513e 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.h @@ -50,6 +50,8 @@ @property (nonatomic, assign) BOOL inverted; /** Focus area of newly-activated text input relative to the window to compare against UIKeyboardFrameBegin/End */ @property (nonatomic, assign) CGRect firstResponderFocus; +/** newly-activated text input outside of the scroll view */ +@property (nonatomic, weak) UIView *firstResponderViewOutsideScrollView; // NOTE: currently these event props are only declared so we can export the // event names to JS - we don't call the blocks directly because scroll events diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 31b76146e61..e83b91de675 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -338,6 +338,12 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu if (!didFocusExternalTextField && focusEnd > endFrame.origin.y) { // Text field active region is below visible area with keyboard - update diff to bring into view contentDiff = endFrame.origin.y - focusEnd; + } else { + UIView *inputAccessoryView = _firstResponderViewOutsideScrollView.inputAccessoryView; + if (inputAccessoryView) { + // Text input view is within the inputAccessoryView. + contentDiff = endFrame.origin.y - beginFrame.origin.y; + } } } else if (endFrame.origin.y <= beginFrame.origin.y) { // Keyboard opened for other reason