diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm index 349e53cef6f790..b9c699b8daaa58 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm @@ -36,6 +36,7 @@ - (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView { 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 12f4209f63a8f8..99b7240b83d7b8 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 6e198a0bfa2d41..bb63afb3c3d7f7 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 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification 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 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification from:self forEvent:nil]) { if (CGRectEqualToRect(_firstResponderFocus, CGRectNull)) { - // Text input view is outside of the scroll view. - return; - } - - CGRect viewIntersection = CGRectIntersection(self.firstResponderFocus, keyboardEndFrame); + 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); - 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 00cf7563d35492..01a066d3c5114f 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 @@ - (void)reactUpdateResponderOffsetForScrollView:(RCTScrollViewComponentView *)sc { 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 d57793b65d9fe7..10ef46a513e143 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 31b76146e61436..e83b91de6756fa 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -338,6 +338,12 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification 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