Skip to content

Commit a96968f

Browse files
authored
Merge pull request #37 from Szymon20000/@szymon/number_of_lines
Add support for numberOfLines and maximumNumberOfLines props on iOS and Android
2 parents 07785f1 + 4bd87ab commit a96968f

31 files changed

+344
-80
lines changed

Libraries/Components/TextInput/AndroidTextInputNativeComponent.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,13 @@ export type NativeProps = $ReadOnly<{|
179179
*/
180180
numberOfLines?: ?Int32,
181181

182+
/**
183+
* Sets the maximum number of lines for a `TextInput`. Use it with multiline set to
184+
* `true` to be able to fill the lines.
185+
* @platform android
186+
*/
187+
maximumNumberOfLines?: ?Int32,
188+
182189
/**
183190
* When `false`, if there is a small amount of space available around a text input
184191
* (e.g. landscape orientation on a phone), the OS may choose to have the user edit

Libraries/Components/TextInput/RCTTextInputViewConfig.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ const RCTTextInputViewConfig = {
138138
placeholder: true,
139139
autoCorrect: true,
140140
multiline: true,
141+
numberOfLines: true,
142+
maximumNumberOfLines: true,
141143
textContentType: true,
142144
maxLength: true,
143145
autoCapitalize: true,

Libraries/Components/TextInput/TextInput.d.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -422,12 +422,6 @@ export interface TextInputAndroidProps {
422422
*/
423423
inlineImagePadding?: number | undefined;
424424

425-
/**
426-
* Sets the number of lines for a TextInput.
427-
* Use it with multiline set to true to be able to fill the lines.
428-
*/
429-
numberOfLines?: number | undefined;
430-
431425
/**
432426
* Sets the return key to the label. Use it instead of `returnKeyType`.
433427
* @platform android
@@ -617,11 +611,29 @@ export interface TextInputProps
617611
*/
618612
maxLength?: number | undefined;
619613

614+
/**
615+
* Sets the maximum number of lines for a TextInput.
616+
* Use it with multiline set to true to be able to fill the lines.
617+
*/
618+
maximumNumberOfLines?: number | undefined;
619+
620620
/**
621621
* If true, the text input can be multiple lines. The default value is false.
622622
*/
623623
multiline?: boolean | undefined;
624624

625+
/**
626+
* Sets the number of lines for a TextInput.
627+
* Use it with multiline set to true to be able to fill the lines.
628+
*/
629+
numberOfLines?: number | undefined;
630+
631+
/**
632+
* Sets the number of rows for a TextInput.
633+
* Use it with multiline set to true to be able to fill the lines.
634+
*/
635+
rows?: number | undefined;
636+
625637
/**
626638
* Callback that is called when the text input is blurred
627639
*/

Libraries/Components/TextInput/TextInput.flow.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -488,26 +488,12 @@ type AndroidProps = $ReadOnly<{|
488488
*/
489489
inlineImagePadding?: ?number,
490490

491-
/**
492-
* Sets the number of lines for a `TextInput`. Use it with multiline set to
493-
* `true` to be able to fill the lines.
494-
* @platform android
495-
*/
496-
numberOfLines?: ?number,
497-
498491
/**
499492
* Sets the return key to the label. Use it instead of `returnKeyType`.
500493
* @platform android
501494
*/
502495
returnKeyLabel?: ?string,
503496

504-
/**
505-
* Sets the number of rows for a `TextInput`. Use it with multiline set to
506-
* `true` to be able to fill the lines.
507-
* @platform android
508-
*/
509-
rows?: ?number,
510-
511497
/**
512498
* When `false`, it will prevent the soft keyboard from showing when the field is focused.
513499
* Defaults to `true`.
@@ -656,6 +642,12 @@ export type Props = $ReadOnly<{|
656642
*/
657643
keyboardType?: ?KeyboardType,
658644

645+
/**
646+
* Sets the maximum number of lines for a `TextInput`. Use it with multiline set to
647+
* `true` to be able to fill the lines.
648+
*/
649+
maximumNumberOfLines?: ?number,
650+
659651
/**
660652
* Specifies largest possible scale a font can reach when `allowFontScaling` is enabled.
661653
* Possible values:
@@ -677,6 +669,12 @@ export type Props = $ReadOnly<{|
677669
*/
678670
multiline?: ?boolean,
679671

672+
/**
673+
* Sets the number of lines for a `TextInput`. Use it with multiline set to
674+
* `true` to be able to fill the lines.
675+
*/
676+
numberOfLines?: ?number,
677+
680678
/**
681679
* Callback that is called when the text input is blurred.
682680
*/
@@ -838,6 +836,12 @@ export type Props = $ReadOnly<{|
838836
*/
839837
returnKeyType?: ?ReturnKeyType,
840838

839+
/**
840+
* Sets the number of rows for a `TextInput`. Use it with multiline set to
841+
* `true` to be able to fill the lines.
842+
*/
843+
rows?: ?number,
844+
841845
/**
842846
* If `true`, the text input obscures the text entered so that sensitive text
843847
* like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'.

Libraries/Components/TextInput/TextInput.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,6 @@ type AndroidProps = $ReadOnly<{|
524524
/**
525525
* Sets the number of lines for a `TextInput`. Use it with multiline set to
526526
* `true` to be able to fill the lines.
527-
* @platform android
528527
*/
529528
numberOfLines?: ?number,
530529

@@ -537,10 +536,14 @@ type AndroidProps = $ReadOnly<{|
537536
/**
538537
* Sets the number of rows for a `TextInput`. Use it with multiline set to
539538
* `true` to be able to fill the lines.
540-
* @platform android
541539
*/
542540
rows?: ?number,
543541

542+
/**
543+
* Sets the maximum number of lines the TextInput can have.
544+
*/
545+
maximumNumberOfLines?: ?number,
546+
544547
/**
545548
* When `false`, it will prevent the soft keyboard from showing when the field is focused.
546549
* Defaults to `true`.
@@ -1082,6 +1085,12 @@ const emptyFunctionThatReturnsTrue = () => true;
10821085
*
10831086
*/
10841087
function InternalTextInput(props: Props): React.Node {
1088+
const {
1089+
rows,
1090+
numberOfLines,
1091+
...otherProps
1092+
} = props;
1093+
10851094
const inputRef = useRef<null | React.ElementRef<HostComponent<mixed>>>(null);
10861095

10871096
// Android sends a "onTextChanged" event followed by a "onSelectionChanged" event, for
@@ -1428,7 +1437,7 @@ function InternalTextInput(props: Props): React.Node {
14281437
textInput = (
14291438
<RCTTextInputView
14301439
ref={_setNativeRef}
1431-
{...props}
1440+
{...otherProps}
14321441
{...eventHandlers}
14331442
accessible={accessible}
14341443
accessibilityState={_accessibilityState}
@@ -1437,6 +1446,7 @@ function InternalTextInput(props: Props): React.Node {
14371446
dataDetectorTypes={props.dataDetectorTypes}
14381447
focusable={focusable}
14391448
mostRecentEventCount={mostRecentEventCount}
1449+
numberOfLines={props.rows ?? props.numberOfLines}
14401450
onBlur={_onBlur}
14411451
onKeyPressSync={props.unstable_onKeyPressSync}
14421452
onChange={_onChange}
@@ -1478,7 +1488,7 @@ function InternalTextInput(props: Props): React.Node {
14781488
* fixed */
14791489
<AndroidTextInput
14801490
ref={_setNativeRef}
1481-
{...props}
1491+
{...otherProps}
14821492
{...eventHandlers}
14831493
accessible={accessible}
14841494
accessibilityState={_accessibilityState}

Libraries/Text/Text.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const Text: React.AbstractComponent<
5959
pressRetentionOffset,
6060
role,
6161
suppressHighlighting,
62+
numberOfLines,
6263
...restProps
6364
} = props;
6465

@@ -196,12 +197,12 @@ const Text: React.AbstractComponent<
196197
}
197198
}
198199

199-
let numberOfLines = restProps.numberOfLines;
200+
let numberOfLinesValue = numberOfLines;
200201
if (numberOfLines != null && !(numberOfLines >= 0)) {
201202
console.error(
202203
`'numberOfLines' in <Text> must be a non-negative number, received: ${numberOfLines}. The value will be set to 0.`,
203204
);
204-
numberOfLines = 0;
205+
numberOfLinesValue = 0;
205206
}
206207

207208
const hasTextAncestor = useContext(TextAncestor);
@@ -233,7 +234,7 @@ const Text: React.AbstractComponent<
233234
isPressable={isPressable}
234235
selectable={_selectable}
235236
nativeID={id ?? nativeID}
236-
numberOfLines={numberOfLines}
237+
maximumNumberOfLines={numberOfLinesValue}
237238
selectionColor={selectionColor}
238239
style={flattenedStyle}
239240
ref={forwardedRef}
@@ -259,7 +260,7 @@ const Text: React.AbstractComponent<
259260
ellipsizeMode={ellipsizeMode ?? 'tail'}
260261
isHighlighted={isHighlighted}
261262
nativeID={id ?? nativeID}
262-
numberOfLines={numberOfLines}
263+
maximumNumberOfLines={numberOfLinesValue}
263264
selectionColor={selectionColor}
264265
style={flattenedStyle}
265266
ref={forwardedRef}

Libraries/Text/Text/RCTTextViewManager.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ @implementation RCTTextViewManager {
2626

2727
RCT_EXPORT_MODULE(RCTText)
2828

29-
RCT_REMAP_SHADOW_PROPERTY(numberOfLines, maximumNumberOfLines, NSInteger)
29+
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
3030
RCT_REMAP_SHADOW_PROPERTY(ellipsizeMode, lineBreakMode, NSLineBreakMode)
3131
RCT_REMAP_SHADOW_PROPERTY(adjustsFontSizeToFit, adjustsFontSizeToFit, BOOL)
3232
RCT_REMAP_SHADOW_PROPERTY(minimumFontScale, minimumFontScale, CGFloat)

Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#import <React/RCTMultilineTextInputView.h>
99
#import <React/RCTMultilineTextInputViewManager.h>
10+
#import <React/RCTUITextView.h>
11+
#import <React/RCTBaseTextInputShadowView.h>
1012

1113
@implementation RCTMultilineTextInputViewManager
1214

@@ -17,8 +19,21 @@ - (UIView *)view
1719
return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge];
1820
}
1921

22+
- (RCTShadowView *)shadowView
23+
{
24+
RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView];
25+
26+
shadowView.maximumNumberOfLines = 0;
27+
shadowView.exactNumberOfLines = 0;
28+
29+
return shadowView;
30+
}
31+
2032
#pragma mark - Multiline <TextInput> (aka TextView) specific properties
2133

2234
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes)
2335

36+
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
37+
RCT_REMAP_SHADOW_PROPERTY(numberOfLines, exactNumberOfLines, NSInteger)
38+
2439
@end

Libraries/Text/TextInput/RCTBaseTextInputShadowView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
1616
@property (nonatomic, copy, nullable) NSString *text;
1717
@property (nonatomic, copy, nullable) NSString *placeholder;
1818
@property (nonatomic, assign) NSInteger maximumNumberOfLines;
19+
@property (nonatomic, assign) NSInteger exactNumberOfLines;
1920
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
2021

2122
- (void)uiManagerWillPerformMounting;

Libraries/Text/TextInput/RCTBaseTextInputShadowView.m

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText
218218

219219
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
220220
{
221-
NSAttributedString *attributedText = [self measurableAttributedText];
221+
NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy];
222+
223+
/*
224+
* The block below is responsible for setting the exact height of the view in lines
225+
* Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines
226+
* prop and then add random lines at the front. However, they are only used for layout
227+
* so they are not visible on the screen.
228+
*/
229+
if (self.exactNumberOfLines) {
230+
NSMutableString *newLines = [NSMutableString stringWithCapacity:self.exactNumberOfLines];
231+
for (NSUInteger i = 0UL; i < self.exactNumberOfLines; ++i) {
232+
[newLines appendString:@"\n"];
233+
}
234+
[attributedText insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:self.textAttributes.effectiveTextAttributes] atIndex:0];
235+
_maximumNumberOfLines = self.exactNumberOfLines;
236+
}
222237

223238
if (!_textStorage) {
224239
_textContainer = [NSTextContainer new];

0 commit comments

Comments
 (0)