diff --git a/BUCK b/BUCK index f6992f1619298c..f9f77064b9cd79 100644 --- a/BUCK +++ b/BUCK @@ -350,6 +350,7 @@ REACT_PUBLIC_HEADERS = { "React/RCTViewUtils.h": RCTVIEWS_PATH + "RCTViewUtils.h", "React/RCTWeakProxy.h": RCTBASE_PATH + "RCTWeakProxy.h", "React/RCTWrapperViewController.h": RCTVIEWS_PATH + "RCTWrapperViewController.h", + "React/UIResponder+FirstResponder.h": RCTVIEWS_PATH + "UIResponder+FirstResponder.h", "React/UIView+React.h": RCTVIEWS_PATH + "UIView+React.h", } diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 0c2ecf2f16846a..61ec1116b727ee 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -191,6 +191,12 @@ type IOSProps = $ReadOnly<{| * @platform ios */ contentInset?: ?EdgeInsetsProp, + /** + * Controls the distance between the soft keyboard and the TextInput + * when using `automaticallyAdjustKeyboardInsets`. + * @platform ios + */ + bottomKeyboardOffset?: ?number, /** * When true, the scroll view bounces when it reaches the end of the * content if the content is larger then the scroll view along the axis of diff --git a/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js b/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js index b1b2df5d84e370..fc0a580ebc7f51 100644 --- a/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js +++ b/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js @@ -23,6 +23,7 @@ export type ScrollViewNativeProps = $ReadOnly<{ automaticallyAdjustContentInsets?: ?boolean, automaticallyAdjustKeyboardInsets?: ?boolean, automaticallyAdjustsScrollIndicatorInsets?: ?boolean, + bottomKeyboardOffset?: ?number, bounces?: ?boolean, bouncesZoom?: ?boolean, canCancelContentTouches?: ?boolean, diff --git a/React/Views/ScrollView/RCTScrollView.h b/React/Views/ScrollView/RCTScrollView.h index 14554f688ac6d6..1f19a734fe004e 100644 --- a/React/Views/ScrollView/RCTScrollView.h +++ b/React/Views/ScrollView/RCTScrollView.h @@ -36,6 +36,7 @@ @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, assign) BOOL automaticallyAdjustKeyboardInsets; +@property (nonatomic, assign) CGFloat bottomKeyboardOffset; @property (nonatomic, assign) BOOL DEPRECATED_sendUpdatedChildFrames; @property (nonatomic, assign) NSTimeInterval scrollEventThrottle; @property (nonatomic, assign) BOOL centerContent; diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index f0f64021aca67e..ad5cdb6141defa 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -20,6 +20,7 @@ #import "RCTViewUtils.h" #import "UIView+Private.h" #import "UIView+React.h" +#import "UIResponder+FirstResponder.h" /** * Include a custom scroll view subclass because we want to limit certain @@ -307,6 +308,7 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification } double duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve curve = (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue]; CGRect beginFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; @@ -324,11 +326,26 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification } CGPoint newContentOffset = _scrollView.contentOffset; - CGFloat contentDiff = endFrame.origin.y - beginFrame.origin.y; - if (self.inverted) { - newContentOffset.y += contentDiff; + UIResponder *firstResponder = [UIResponder currentFirstResponder]; + if ([firstResponder isKindOfClass: [UITextField class]] && [(UITextField *) firstResponder isDescendantOfView:_scrollView]) { + UITextField *textField = [UIResponder currentFirstResponder]; + CGRect textFieldFrame = [textField.superview convertRect:textField.frame toView:nil]; + CGFloat textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height + _bottomKeyboardOffset; + CGFloat contentDiff = textFieldBottom - endFrame.origin.y; + if (textFieldBottom > endFrame.origin.y && endFrame.origin.y < beginFrame.origin.y) { + if (self.inverted) { + newContentOffset.y -= contentDiff; + } else { + newContentOffset.y += contentDiff; + } + } } else { - newContentOffset.y -= contentDiff; + CGFloat contentDiff = endFrame.origin.y - beginFrame.origin.y; + if (self.inverted) { + newContentOffset.y += contentDiff; + } else { + newContentOffset.y -= contentDiff; + } } [UIView animateWithDuration:duration diff --git a/React/Views/ScrollView/RCTScrollViewManager.m b/React/Views/ScrollView/RCTScrollViewManager.m index 37f6416c7cc6d4..9203fd9c5e126a 100644 --- a/React/Views/ScrollView/RCTScrollViewManager.m +++ b/React/Views/ScrollView/RCTScrollViewManager.m @@ -67,6 +67,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(maintainVisibleContentPosition, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustKeyboardInsets, BOOL) +RCT_EXPORT_VIEW_PROPERTY(bottomKeyboardOffset, CGFloat) RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat) RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(indicatorStyle, UIScrollViewIndicatorStyle) diff --git a/React/Views/UIResponder+FirstResponder.h b/React/Views/UIResponder+FirstResponder.h new file mode 100644 index 00000000000000..0f8b028d01c50a --- /dev/null +++ b/React/Views/UIResponder+FirstResponder.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + +#import + +@interface UIResponder (FirstResponder) + +(id)currentFirstResponder; +@end diff --git a/React/Views/UIResponder+FirstResponder.m b/React/Views/UIResponder+FirstResponder.m new file mode 100644 index 00000000000000..5178ba893fbf9d --- /dev/null +++ b/React/Views/UIResponder+FirstResponder.m @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "UIResponder+FirstResponder.h" + +static __weak id currentFirstResponder; + +@implementation UIResponder (FirstResponder) + ++(id)currentFirstResponder { + currentFirstResponder = nil; + [[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:nil forEvent:nil]; + return currentFirstResponder; +} + +-(void)findFirstResponder:(id)sender { + currentFirstResponder = self; +} + +@end