From dcaa33e6d9d59b7d32bbdc14a91b297b6d68cee1 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Mon, 24 Feb 2025 04:23:58 -0800 Subject: [PATCH] Add support for `numberOfLines` for `TextInput` on iOS (#49549) Summary: `TextInput` component has been missing support for `numberOfLines` prop on iOS, this PR adds it. ## Changelog: [IOS] [ADDED] - Add support for `numberOfLines` prop on `TextInput` Pull Request resolved: https://github.com/facebook/react-native/pull/49549 Test Plan: Tested on RNTester and added a new case utilizing the prop Reviewed By: cipolleschi Differential Revision: D69915133 Pulled By: j-piasecki fbshipit-source-id: b6a86bc64bd3c2129a64e99c9bcec9cf5bfde3bc --- .../Components/TextInput/RCTTextInputViewConfig.js | 1 + .../Libraries/Components/TextInput/TextInput.js | 1 + .../textlayoutmanager/RCTTextLayoutManager.mm | 11 +++++++++++ .../js/examples/TextInput/TextInputExample.ios.js | 8 ++++++-- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js index 1af4104f5f0..fe5c4913a83 100644 --- a/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +++ b/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js @@ -142,6 +142,7 @@ const RCTTextInputViewConfig = { placeholder: true, autoCorrect: true, multiline: true, + numberOfLines: true, textContentType: true, maxLength: true, autoCapitalize: true, diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index c8dff5c5729..29bee1c07c1 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -1626,6 +1626,7 @@ function InternalTextInput(props: TextInputProps): React.Node { focusable={tabIndex !== undefined ? !tabIndex : focusable} mostRecentEventCount={mostRecentEventCount} nativeID={id ?? props.nativeID} + numberOfLines={props.rows ?? props.numberOfLines} onBlur={_onBlur} onChange={_onChange} onContentSizeChange={props.onContentSizeChange} diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index 9ffe117c4e1..ae68babaffc 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -232,6 +232,17 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi layoutManager.usesFontLeading = NO; [layoutManager addTextContainer:textContainer]; + // A workaround for the issue with empty line measurement: + // When maximumNumberOfLines is set to N and N+1 line is empty, the returned + // measurement is for N+1 lines. Adding any character to that line results + // in the correct measurement. + if (attributedString.length > 0 && [[attributedString string] characterAtIndex:attributedString.length - 1] == '\n') { + NSMutableAttributedString *mutableString = + [[NSMutableAttributedString alloc] initWithAttributedString:attributedString]; + [mutableString replaceCharactersInRange:NSMakeRange(attributedString.length - 1, 1) withString:@"\n "]; + attributedString = mutableString; + } + NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; RCTApplyBaselineOffset(textStorage); diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index c04b37a6189..c2004ddcb87 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -304,7 +304,6 @@ function KeyboardShortcutsExample() { const styles = StyleSheet.create({ multiline: { height: 50, - marginBottom: 4, }, multilinePlaceholderStyles: { letterSpacing: 10, @@ -598,7 +597,7 @@ const textInputExamples: Array = [ title: 'Multiline', render: function (): React.Node { return ( - + = [ multiline={true} style={styles.multiline} /> +