From 6cd100a99bf97c3b1a4c7dc008236f5af34123dd Mon Sep 17 00:00:00 2001 From: "isidoro.ferreiro" Date: Sun, 6 Nov 2022 20:07:35 +0000 Subject: [PATCH 01/19] Automatic keyboard insets improvements --- BUCK | 1 + React/Views/ScrollView/RCTScrollView.m | 26 ++++++++++++++++++++---- React/Views/UIResponder+FirstResponder.h | 13 ++++++++++++ React/Views/UIResponder+FirstResponder.m | 24 ++++++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 React/Views/UIResponder+FirstResponder.h create mode 100644 React/Views/UIResponder+FirstResponder.m 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/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index f0f64021aca67e..271e14c16abff6 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 @@ -324,11 +325,28 @@ - (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]; + // [32] is a placeholder. It's the distance between the bottom of the textfield and the top of the keyboard + // What value should be used? Should it be customizable with a prop? + CGFloat textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height + 32; + CGFloat contentDiff = textFieldBottom - endFrame.origin.y; + if (textFieldBottom > endFrame.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/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 From 24ce8b590789805679632d474ed59b1b821862ce Mon Sep 17 00:00:00 2001 From: "isidoro.ferreiro" Date: Thu, 17 Nov 2022 11:38:14 +0000 Subject: [PATCH 02/19] Prevent unintended scrolls on native navigation --- React/Views/ScrollView/RCTScrollView.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index 271e14c16abff6..c6e55f92a05635 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -21,6 +21,7 @@ #import "UIView+Private.h" #import "UIView+React.h" #import "UIResponder+FirstResponder.h" +#import "RCTInputAccessoryView.h" /** * Include a custom scroll view subclass because we want to limit certain @@ -308,6 +309,8 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification } double duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + if (duration == 0) return; + UIViewAnimationCurve curve = (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue]; CGRect beginFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; @@ -333,14 +336,16 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification // What value should be used? Should it be customizable with a prop? CGFloat textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height + 32; CGFloat contentDiff = textFieldBottom - endFrame.origin.y; - if (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 { + } else { + return; + } + } else if ([firstResponder isKindOfClass: [UITextField class]] || [firstResponder isKindOfClass: [RCTInputAccessoryView class]]) { CGFloat contentDiff = endFrame.origin.y - beginFrame.origin.y; if (self.inverted) { newContentOffset.y += contentDiff; From 78833158330640fb797b3ad333db88169263958b Mon Sep 17 00:00:00 2001 From: "isidoro.ferreiro" Date: Thu, 17 Nov 2022 14:37:57 +0000 Subject: [PATCH 03/19] Add bottomKeyboardOffset prop --- Libraries/Components/ScrollView/ScrollView.js | 6 ++++++ .../Components/ScrollView/ScrollViewNativeComponentType.js | 1 + React/Views/ScrollView/RCTScrollView.h | 1 + React/Views/ScrollView/RCTScrollView.m | 4 +--- React/Views/ScrollView/RCTScrollViewManager.m | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 0c2ecf2f16846a..96e060794013fb 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 c6e55f92a05635..a268dad61bff0c 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -332,9 +332,7 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification if ([firstResponder isKindOfClass: [UITextField class]] && [(UITextField *) firstResponder isDescendantOfView:_scrollView]) { UITextField *textField = [UIResponder currentFirstResponder]; CGRect textFieldFrame = [textField.superview convertRect:textField.frame toView:nil]; - // [32] is a placeholder. It's the distance between the bottom of the textfield and the top of the keyboard - // What value should be used? Should it be customizable with a prop? - CGFloat textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height + 32; + 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) { 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) From 42088b6e7c7b74f292ee35df1c24642b711006dd Mon Sep 17 00:00:00 2001 From: "isidoro.ferreiro" Date: Thu, 17 Nov 2022 14:41:32 +0000 Subject: [PATCH 04/19] formatting --- Libraries/Components/ScrollView/ScrollView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 96e060794013fb..61ec1116b727ee 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -196,7 +196,7 @@ type IOSProps = $ReadOnly<{| * when using `automaticallyAdjustKeyboardInsets`. * @platform ios */ - bottomKeyboardOffset?: ?number, + 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 From e2891a7eafe4ba4d447322ca0cca19814a6a0d18 Mon Sep 17 00:00:00 2001 From: "isidoro.ferreiro" Date: Thu, 17 Nov 2022 18:46:23 +0000 Subject: [PATCH 05/19] Update RCTScrollView.m --- React/Views/ScrollView/RCTScrollView.m | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index a268dad61bff0c..ad5cdb6141defa 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -21,7 +21,6 @@ #import "UIView+Private.h" #import "UIView+React.h" #import "UIResponder+FirstResponder.h" -#import "RCTInputAccessoryView.h" /** * Include a custom scroll view subclass because we want to limit certain @@ -309,7 +308,6 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification } double duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - if (duration == 0) return; UIViewAnimationCurve curve = (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue]; @@ -340,10 +338,8 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification } else { newContentOffset.y += contentDiff; } - } else { - return; } - } else if ([firstResponder isKindOfClass: [UITextField class]] || [firstResponder isKindOfClass: [RCTInputAccessoryView class]]) { + } else { CGFloat contentDiff = endFrame.origin.y - beginFrame.origin.y; if (self.inverted) { newContentOffset.y += contentDiff; From 4b9e418cc427c309f7e6556e9d05db9e153501a4 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Thu, 8 Jun 2023 10:01:18 +0200 Subject: [PATCH 06/19] Add ScrollView keyboard insets example to RNTester --- .../ScrollViewKeyboardInsetsExample.ios.js | 117 ++++++++++++++++++ .../rn-tester/js/utils/RNTesterList.ios.js | 4 + 2 files changed, 121 insertions(+) create mode 100644 packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js new file mode 100644 index 00000000000000..85e96dbdfbadea --- /dev/null +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js @@ -0,0 +1,117 @@ +/** + * 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. + */ + +'use strict'; + +import * as React from 'react'; + +import { + ScrollView, + StyleSheet, + Switch, + Text, + TextInput, + View, +} from 'react-native'; + +export function ScrollViewKeyboardInsetsExample() { + const [automaticallyAdjustKeyboardInsets, setAutomaticallyAdjustKeyboardInsets] = React.useState(true); + const [bottomKeyboardOffset, setBottomKeyboardOffset] = React.useState(0); + + return ( + + + automaticallyAdjustKeyboardInsets is {automaticallyAdjustKeyboardInsets + ''} + setAutomaticallyAdjustKeyboardInsets(v)} + value={automaticallyAdjustKeyboardInsets} + style={styles.controlSwitch}/> + + + bottomKeyboardOffset + setBottomKeyboardOffset(parseInt(v || '0', 10))} + style={styles.controlTextInput}/> + + + {[...Array(20).keys()].map(item => ( + + + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'stretch', + justifyContent: 'flex-start', + }, + scrollViewContent: { + paddingVertical: 5, + paddingHorizontal: 10, + }, + textInputRow: { + borderWidth: 1, + marginVertical: 8, + borderColor: '#999', + }, + textInput: { + width: '100%', + backgroundColor: '#fff', + fontSize: 24, + padding: 8, + }, + controlRow: { + padding: 10, + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + backgroundColor: '#fff', + borderTopWidth: 1, + borderTopColor: '#ccc', + borderBottomWidth: 1, + borderBottomColor: '#ccc', + }, + controlSwitch: { + }, + controlTextInput: { + flex: 1, + paddingVertical: 10, + paddingHorizontal: 10, + marginVertical: -10, + marginRight: -10, + fontSize: 20, + textAlign: 'right', + }, + code: { + fontSize: 12, + fontFamily: 'Courier', + }, +}); + +exports.title = 'ScrollViewKeyboardInsets'; +exports.category = 'iOS'; +exports.description = + 'ScrollView automaticallyAdjustKeyboardInsets adjusts keyboard insets when soft keyboard is activated.'; +exports.examples = [ + { + title: ' automaticallyAdjustKeyboardInsets Example', + render: (): React.Node => , + }, +]; diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js index 019887eb9e08d4..04f8b4a003c18c 100644 --- a/packages/rn-tester/js/utils/RNTesterList.ios.js +++ b/packages/rn-tester/js/utils/RNTesterList.ios.js @@ -89,6 +89,10 @@ const Components: Array = [ key: 'ScrollViewIndicatorInsetsExample', module: require('../examples/ScrollView/ScrollViewIndicatorInsetsExample'), }, + { + key: 'ScrollViewKeyboardInsetsExample', + module: require('../examples/ScrollView/ScrollViewKeyboardInsetsExample'), + }, { key: 'SectionListIndex', module: require('../examples/SectionList/SectionListIndex'), From ee6bbc373b341afb8fe4a34cc98301499a82783e Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Thu, 15 Jun 2023 14:40:36 +0200 Subject: [PATCH 07/19] Remove scroll view bottom keyboard offset property --- .../Libraries/Components/ScrollView/ScrollView.js | 6 ------ .../ScrollView/ScrollViewNativeComponentType.js | 1 - .../React/Views/ScrollView/RCTScrollView.h | 1 - .../React/Views/ScrollView/RCTScrollView.m | 2 +- .../React/Views/ScrollView/RCTScrollViewManager.m | 1 - .../ScrollView/ScrollViewKeyboardInsetsExample.ios.js | 10 ---------- 6 files changed, 1 insertion(+), 20 deletions(-) diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js index dfe55764579ade..096874e7f2c0e1 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js @@ -200,12 +200,6 @@ 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/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js index fc0a580ebc7f51..b1b2df5d84e370 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js @@ -23,7 +23,6 @@ export type ScrollViewNativeProps = $ReadOnly<{ automaticallyAdjustContentInsets?: ?boolean, automaticallyAdjustKeyboardInsets?: ?boolean, automaticallyAdjustsScrollIndicatorInsets?: ?boolean, - bottomKeyboardOffset?: ?number, bounces?: ?boolean, bouncesZoom?: ?boolean, canCancelContentTouches?: ?boolean, diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.h b/packages/react-native/React/Views/ScrollView/RCTScrollView.h index 1f19a734fe004e..14554f688ac6d6 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.h @@ -36,7 +36,6 @@ @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/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 7cb6cc3dca7c69..17883f930f1355 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -330,7 +330,7 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification 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 textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height; CGFloat contentDiff = textFieldBottom - endFrame.origin.y; if (textFieldBottom > endFrame.origin.y && endFrame.origin.y < beginFrame.origin.y) { if (self.inverted) { diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m index 9203fd9c5e126a..37f6416c7cc6d4 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m @@ -67,7 +67,6 @@ - (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/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js index 85e96dbdfbadea..d28364cb3ad668 100644 --- a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js @@ -20,7 +20,6 @@ import { export function ScrollViewKeyboardInsetsExample() { const [automaticallyAdjustKeyboardInsets, setAutomaticallyAdjustKeyboardInsets] = React.useState(true); - const [bottomKeyboardOffset, setBottomKeyboardOffset] = React.useState(0); return ( @@ -31,20 +30,11 @@ export function ScrollViewKeyboardInsetsExample() { value={automaticallyAdjustKeyboardInsets} style={styles.controlSwitch}/> - - bottomKeyboardOffset - setBottomKeyboardOffset(parseInt(v || '0', 10))} - style={styles.controlTextInput}/> - {[...Array(20).keys()].map(item => ( From 12376c519a283a4d625f2d043556f569da704132 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 16 Jun 2023 00:09:13 +0200 Subject: [PATCH 08/19] Add multiline inputs to keyboard insets tester --- .../ScrollViewKeyboardInsetsExample.ios.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js index d28364cb3ad668..caace788455e51 100644 --- a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js @@ -36,11 +36,16 @@ export function ScrollViewKeyboardInsetsExample() { ]} automaticallyAdjustKeyboardInsets={automaticallyAdjustKeyboardInsets} keyboardDismissMode={'interactive'}> - {[...Array(20).keys()].map(item => ( - - - - ))} + {[...Array(20).keys()].map((item, index) => { + const largeInput = (index % 5) === 4; + return ( + + + + ); + })} ); @@ -67,6 +72,9 @@ const styles = StyleSheet.create({ fontSize: 24, padding: 8, }, + textInputLarger: { + minHeight: 200, + }, controlRow: { padding: 10, alignItems: 'center', From 915be16e860a50029fc9924ebfa1aa5e5b6f5251 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 16 Jun 2023 00:09:27 +0200 Subject: [PATCH 09/19] Add external text input to keyboard insets tester --- .../ScrollView/ScrollViewKeyboardInsetsExample.ios.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js index caace788455e51..f57b0474d83418 100644 --- a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js @@ -30,6 +30,9 @@ export function ScrollViewKeyboardInsetsExample() { value={automaticallyAdjustKeyboardInsets} style={styles.controlSwitch}/> + + + Date: Fri, 16 Jun 2023 00:13:15 +0200 Subject: [PATCH 10/19] Improve scroll view keyboard insets text handling --- .../Text/TextInput/RCTBaseTextInputView.m | 28 +++++++++++++++++++ .../React/Views/ScrollView/RCTScrollView.h | 1 + .../React/Views/ScrollView/RCTScrollView.m | 24 +++++++--------- .../React/Views/UIResponder+FirstResponder.h | 13 --------- .../React/Views/UIResponder+FirstResponder.m | 24 ---------------- 5 files changed, 39 insertions(+), 51 deletions(-) delete mode 100644 packages/react-native/React/Views/UIResponder+FirstResponder.h delete mode 100644 packages/react-native/React/Views/UIResponder+FirstResponder.m diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m index 9da656c5b0fb95..9f0a9799160363 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -10,6 +10,7 @@ #import #import #import +#import #import #import #import @@ -19,6 +20,8 @@ #import #import +#define EMPTY_TEXT_KEYBOARD_BOTTOM_OFFSET 15 // Native iOS empty text field bottom keyboard offset amount + @implementation RCTBaseTextInputView { __weak RCTBridge *_bridge; __weak id _eventDispatcher; @@ -27,6 +30,31 @@ @implementation RCTBaseTextInputView { BOOL _didMoveToWindow; } +- (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView +{ + if (![self isDescendantOfView:scrollView]) { + // View is outside scroll view + return; + } + + UITextRange *selectedTextRange = self.backedTextInputView.selectedTextRange; + UITextSelectionRect *selection = [self.backedTextInputView selectionRectsForRange:selectedTextRange].firstObject; + if (selection == nil) { + // No active selection or caret - fallback to entire input frame + scrollView.firstResponderFocus = [self convertRect:self.frame toView:nil]; + return; + } + + // Focus on text selection frame + CGRect focusRect = CGRectMake( + selection.rect.origin.x, + selection.rect.origin.y, + selection.rect.size.width, + selection.rect.size.height + (selectedTextRange.empty ? EMPTY_TEXT_KEYBOARD_BOTTOM_OFFSET : 0) + ); + scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil]; +} + - (instancetype)initWithBridge:(RCTBridge *)bridge { RCTAssertParam(bridge); diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.h b/packages/react-native/React/Views/ScrollView/RCTScrollView.h index 14554f688ac6d6..89364feae5568d 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.h @@ -48,6 +48,7 @@ @property (nonatomic, assign) BOOL snapToEnd; @property (nonatomic, copy) NSString *snapToAlignment; @property (nonatomic, assign) BOOL inverted; +@property (nonatomic, assign) CGRect firstResponderFocus; // 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 17883f930f1355..942a30d66da1c1 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -20,7 +20,6 @@ #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 @@ -326,20 +325,17 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification } CGPoint newContentOffset = _scrollView.contentOffset; - 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; - 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; - } + self.firstResponderFocus = CGRectNull; + + if ([[UIApplication sharedApplication] sendAction:@selector(reactUpdateResponderOffsetForScrollView:) to:nil from:self forEvent:nil]) { + // Inner text field focused + CGFloat focusEnd = self.firstResponderFocus.origin.y + self.firstResponderFocus.size.height; + if (focusEnd > endFrame.origin.y) { + // Text field active region is below visible area with keyboard - update offset to bring into view + newContentOffset.y += focusEnd - endFrame.origin.y; } - } else { + } else if (endFrame.origin.y <= beginFrame.origin.y) { + // Keyboard opened for other reason CGFloat contentDiff = endFrame.origin.y - beginFrame.origin.y; if (self.inverted) { newContentOffset.y += contentDiff; diff --git a/packages/react-native/React/Views/UIResponder+FirstResponder.h b/packages/react-native/React/Views/UIResponder+FirstResponder.h deleted file mode 100644 index 0f8b028d01c50a..00000000000000 --- a/packages/react-native/React/Views/UIResponder+FirstResponder.h +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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/packages/react-native/React/Views/UIResponder+FirstResponder.m b/packages/react-native/React/Views/UIResponder+FirstResponder.m deleted file mode 100644 index 5178ba893fbf9d..00000000000000 --- a/packages/react-native/React/Views/UIResponder+FirstResponder.m +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 From 08078a45d6a968723f6acad744042d4b06f94ad0 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 16 Jun 2023 06:57:11 +0200 Subject: [PATCH 11/19] Prevent scrolling when focusing external textfield --- packages/react-native/React/Views/ScrollView/RCTScrollView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 942a30d66da1c1..8d6caee9c282fb 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -330,7 +330,7 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification if ([[UIApplication sharedApplication] sendAction:@selector(reactUpdateResponderOffsetForScrollView:) to:nil from:self forEvent:nil]) { // Inner text field focused CGFloat focusEnd = self.firstResponderFocus.origin.y + self.firstResponderFocus.size.height; - if (focusEnd > endFrame.origin.y) { + if (focusEnd > endFrame.origin.y && focusEnd != INFINITY) { // Text field active region is below visible area with keyboard - update offset to bring into view newContentOffset.y += focusEnd - endFrame.origin.y; } From 62fc88acfae7ca896b281ae1aab2ebfe48413eef Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 16 Jun 2023 06:57:41 +0200 Subject: [PATCH 12/19] Handle textfield focus in inverted scroll views --- .../React/Views/ScrollView/RCTScrollView.m | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 8d6caee9c282fb..4458ff41890c52 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -327,21 +327,22 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification CGPoint newContentOffset = _scrollView.contentOffset; self.firstResponderFocus = CGRectNull; + CGFloat contentDiff = 0; if ([[UIApplication sharedApplication] sendAction:@selector(reactUpdateResponderOffsetForScrollView:) to:nil from:self forEvent:nil]) { // Inner text field focused CGFloat focusEnd = self.firstResponderFocus.origin.y + self.firstResponderFocus.size.height; if (focusEnd > endFrame.origin.y && focusEnd != INFINITY) { - // Text field active region is below visible area with keyboard - update offset to bring into view - newContentOffset.y += 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 if (endFrame.origin.y <= beginFrame.origin.y) { // Keyboard opened for other reason - CGFloat contentDiff = endFrame.origin.y - beginFrame.origin.y; - if (self.inverted) { - newContentOffset.y += contentDiff; - } else { - newContentOffset.y -= contentDiff; - } + contentDiff = endFrame.origin.y - beginFrame.origin.y; + } + if (self.inverted) { + newContentOffset.y += contentDiff; + } else { + newContentOffset.y -= contentDiff; } if (@available(iOS 14.0, *)) { From 5e895f2fe9dae5b548955cc79d6ba85a90e63827 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 16 Jun 2023 06:58:38 +0200 Subject: [PATCH 13/19] Support FlatLists in keyboard insets tester --- .../ScrollViewKeyboardInsetsExample.ios.js | 68 ++++++++++++++----- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js index f57b0474d83418..55225966c01f4b 100644 --- a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js @@ -11,6 +11,7 @@ import * as React from 'react'; import { ScrollView, + FlatList, StyleSheet, Switch, Text, @@ -20,6 +21,26 @@ import { export function ScrollViewKeyboardInsetsExample() { const [automaticallyAdjustKeyboardInsets, setAutomaticallyAdjustKeyboardInsets] = React.useState(true); + const [flatList, setFlatList] = React.useState(false); + const [inverted, setInverted] = React.useState(false); + + const scrollViewProps = { + contentContainerStyle: styles.scrollViewContent, + automaticallyAdjustKeyboardInsets: automaticallyAdjustKeyboardInsets, + keyboardDismissMode: 'interactive', + }; + + const data = [...Array(20).keys()]; + const renderItem = ({ item, index }) => { + const largeInput = (index % 5) === 4; + return ( + + + + ); + }; return ( @@ -30,26 +51,39 @@ export function ScrollViewKeyboardInsetsExample() { value={automaticallyAdjustKeyboardInsets} style={styles.controlSwitch}/> + + FlatList is {flatList + ''} + setFlatList(v)} + value={flatList} + style={styles.controlSwitch}/> + + {flatList && ( + + inverted is {inverted + ''} + setInverted(v)} + value={inverted} + style={styles.controlSwitch}/> + + )} - - {[...Array(20).keys()].map((item, index) => { - const largeInput = (index % 5) === 4; - return ( - - - - ); - })} - + {flatList + ? ( + + ) + : ( + + {data.map((item, index) => renderItem({ item, index }))} + + ) + } ); } From 87981301cf7300c7dca5aef3ebeb4a3ee9c1ff10 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Mon, 19 Jun 2023 14:15:11 +0200 Subject: [PATCH 14/19] Apply requested code format changes --- .../Text/TextInput/RCTBaseTextInputView.m | 24 +++++++++---------- .../React/Views/ScrollView/RCTScrollView.m | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m index 9f0a9799160363..3b788b19b3fe74 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -20,7 +20,8 @@ #import #import -#define EMPTY_TEXT_KEYBOARD_BOTTOM_OFFSET 15 // Native iOS empty text field bottom keyboard offset amount +/** Native iOS empty text field bottom keyboard offset amount */ +static const CGFloat kEmptyKeyboardBottomOffset = 15.0; @implementation RCTBaseTextInputView { __weak RCTBridge *_bridge; @@ -41,18 +42,17 @@ - (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView UITextSelectionRect *selection = [self.backedTextInputView selectionRectsForRange:selectedTextRange].firstObject; if (selection == nil) { // No active selection or caret - fallback to entire input frame - scrollView.firstResponderFocus = [self convertRect:self.frame toView:nil]; - return; + scrollView.firstResponderFocus = [self convertRect:self.bounds toView:nil]; + } else { + // Focus on text selection frame + CGRect focusRect = CGRectMake( + selection.rect.origin.x, + selection.rect.origin.y, + selection.rect.size.width, + selection.rect.size.height + (selectedTextRange.empty ? kEmptyKeyboardBottomOffset : 0) + ); + scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil]; } - - // Focus on text selection frame - CGRect focusRect = CGRectMake( - selection.rect.origin.x, - selection.rect.origin.y, - selection.rect.size.width, - selection.rect.size.height + (selectedTextRange.empty ? EMPTY_TEXT_KEYBOARD_BOTTOM_OFFSET : 0) - ); - scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil]; } - (instancetype)initWithBridge:(RCTBridge *)bridge diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 4458ff41890c52..d9ff6605839d69 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -330,7 +330,7 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification CGFloat contentDiff = 0; if ([[UIApplication sharedApplication] sendAction:@selector(reactUpdateResponderOffsetForScrollView:) to:nil from:self forEvent:nil]) { // Inner text field focused - CGFloat focusEnd = self.firstResponderFocus.origin.y + self.firstResponderFocus.size.height; + CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus); if (focusEnd > endFrame.origin.y && focusEnd != INFINITY) { // Text field active region is below visible area with keyboard - update diff to bring into view contentDiff = endFrame.origin.y - focusEnd; From ca5ead83af5221a8f2f4cff5d23b02793c5835fb Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Mon, 19 Jun 2023 14:15:57 +0200 Subject: [PATCH 15/19] Improve native text field keyboard offsetting --- .../Libraries/Text/TextInput/RCTBaseTextInputView.m | 7 ++++--- .../react-native/React/Views/ScrollView/RCTScrollView.m | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m index 3b788b19b3fe74..6ca49e008ee274 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -20,8 +20,8 @@ #import #import -/** Native iOS empty text field bottom keyboard offset amount */ -static const CGFloat kEmptyKeyboardBottomOffset = 15.0; +/** Native iOS text field bottom keyboard offset amount */ +static const CGFloat kSingleLineKeyboardBottomOffset = 15.0; @implementation RCTBaseTextInputView { __weak RCTBridge *_bridge; @@ -45,11 +45,12 @@ - (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView scrollView.firstResponderFocus = [self convertRect:self.bounds toView:nil]; } else { // Focus on text selection frame + BOOL isMultiline = [self.backedTextInputView isKindOfClass:[UITextView class]]; CGRect focusRect = CGRectMake( selection.rect.origin.x, selection.rect.origin.y, selection.rect.size.width, - selection.rect.size.height + (selectedTextRange.empty ? kEmptyKeyboardBottomOffset : 0) + selection.rect.size.height + (isMultiline ? 0 : kSingleLineKeyboardBottomOffset) ); scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil]; } diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index d9ff6605839d69..8b56c37ddb6f61 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -331,7 +331,8 @@ - (void)_keyboardWillChangeFrame:(NSNotification *)notification if ([[UIApplication sharedApplication] sendAction:@selector(reactUpdateResponderOffsetForScrollView:) to:nil from:self forEvent:nil]) { // Inner text field focused CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus); - if (focusEnd > endFrame.origin.y && focusEnd != INFINITY) { + BOOL didFocusExternalTextField = focusEnd == INFINITY; + 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; } From 669dcff1568c00b345aef2ef485efa9f861e9de0 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 14 Jul 2023 13:15:23 +0200 Subject: [PATCH 16/19] Simplify calculating text input focus area --- .../Text/TextInput/RCTBaseTextInputView.m | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m index 6ca49e008ee274..dcaa2ccb1f5884 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -40,20 +40,19 @@ - (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView UITextRange *selectedTextRange = self.backedTextInputView.selectedTextRange; UITextSelectionRect *selection = [self.backedTextInputView selectionRectsForRange:selectedTextRange].firstObject; + CGRect focusRect; if (selection == nil) { // No active selection or caret - fallback to entire input frame - scrollView.firstResponderFocus = [self convertRect:self.bounds toView:nil]; + focusRect = self.bounds; } else { // Focus on text selection frame + focusRect = selection.rect; BOOL isMultiline = [self.backedTextInputView isKindOfClass:[UITextView class]]; - CGRect focusRect = CGRectMake( - selection.rect.origin.x, - selection.rect.origin.y, - selection.rect.size.width, - selection.rect.size.height + (isMultiline ? 0 : kSingleLineKeyboardBottomOffset) - ); - scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil]; + if (!isMultiline) { + focusRect.size.height += kSingleLineKeyboardBottomOffset; + } } + scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil]; } - (instancetype)initWithBridge:(RCTBridge *)bridge From cf0856560f6733c5af53f4a8fd768cd1b98d0cad Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 14 Jul 2023 13:39:14 +0200 Subject: [PATCH 17/19] Document new scroll view property --- packages/react-native/React/Views/ScrollView/RCTScrollView.h | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.h b/packages/react-native/React/Views/ScrollView/RCTScrollView.h index 89364feae5568d..d103fe28cf77d5 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.h +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.h @@ -48,6 +48,7 @@ @property (nonatomic, assign) BOOL snapToEnd; @property (nonatomic, copy) NSString *snapToAlignment; @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; // NOTE: currently these event props are only declared so we can export the From f6435530c577a09d79381c86212200163250c9f7 Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Fri, 14 Jul 2023 13:41:14 +0200 Subject: [PATCH 18/19] Add scroll view keyboard inset height test option --- .../ScrollViewKeyboardInsetsExample.ios.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js index 55225966c01f4b..04ace36d56c323 100644 --- a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js +++ b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js @@ -23,8 +23,10 @@ export function ScrollViewKeyboardInsetsExample() { const [automaticallyAdjustKeyboardInsets, setAutomaticallyAdjustKeyboardInsets] = React.useState(true); const [flatList, setFlatList] = React.useState(false); const [inverted, setInverted] = React.useState(false); + const [heightRestricted, setHeightRestricted] = React.useState(false); const scrollViewProps = { + style: heightRestricted && styles.scrollViewHeightRestricted, contentContainerStyle: styles.scrollViewContent, automaticallyAdjustKeyboardInsets: automaticallyAdjustKeyboardInsets, keyboardDismissMode: 'interactive', @@ -67,6 +69,13 @@ export function ScrollViewKeyboardInsetsExample() { style={styles.controlSwitch}/> )} + + HeightRestricted is {heightRestricted + ''} + setHeightRestricted(v)} + value={heightRestricted} + style={styles.controlSwitch}/> + @@ -94,6 +103,11 @@ const styles = StyleSheet.create({ alignItems: 'stretch', justifyContent: 'flex-start', }, + scrollViewHeightRestricted: { + marginVertical: 50, + borderColor: '#f00', + borderWidth: 1, + }, scrollViewContent: { paddingVertical: 5, paddingHorizontal: 10, From c2eab2c0142f300b22cba2496219d5e8410e661b Mon Sep 17 00:00:00 2001 From: Adam Averay Date: Thu, 14 Sep 2023 09:57:25 +1000 Subject: [PATCH 19/19] Rename iOS-specific example implementation --- ...setsExample.ios.js => ScrollViewKeyboardInsetsIOSExample.js} | 0 packages/rn-tester/js/utils/RNTesterList.ios.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/rn-tester/js/examples/ScrollView/{ScrollViewKeyboardInsetsExample.ios.js => ScrollViewKeyboardInsetsIOSExample.js} (100%) diff --git a/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js b/packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsIOSExample.js similarity index 100% rename from packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsExample.ios.js rename to packages/rn-tester/js/examples/ScrollView/ScrollViewKeyboardInsetsIOSExample.js diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js index a13d29fec11108..fa0587ccd1454c 100644 --- a/packages/rn-tester/js/utils/RNTesterList.ios.js +++ b/packages/rn-tester/js/utils/RNTesterList.ios.js @@ -91,7 +91,7 @@ const Components: Array = [ }, { key: 'ScrollViewKeyboardInsetsExample', - module: require('../examples/ScrollView/ScrollViewKeyboardInsetsExample'), + module: require('../examples/ScrollView/ScrollViewKeyboardInsetsIOSExample'), }, { key: 'SectionListIndex',