Skip to content

Commit f864b61

Browse files
fix(ios): center TextInput text when lineHeight > fontSize on Fabric
Summary: Ports the Paper-only fix from facebook#38359 to the Fabric (new arch) path. When `lineHeight` is set larger than the font's intrinsic line height, UIKit anchors glyphs to the bottom of the attributed-string line box, making typed text visually sit too low while the placeholder (which uses a different rendering path) stays correctly centered. The `RCTApplyBaselineOffset` helper that Paper's fix relies on already ships in Fabric (`RCTAttributedTextUtils.mm`) and is invoked for `<Text>` rendering in `RCTTextLayoutManager`, but was never wired into the `<TextInput>` path. This calls it inside `RCTTextInputComponentView._setAttributedString:`. This regression against Paper is tracked in facebook#39145. It has been reported repeatedly for the new architecture (see comments referring back to facebook#38359). Changelog: [IOS] [FIXED] - Center typed TextInput text when lineHeight > fontSize on the new architecture. Test Plan: Added an RN Tester example ("Single-line lineHeight baseline" under TextInput examples) that renders a single-line TextInput at a range of lineHeight values with a fixed fontSize. Before this change, typing into any of these inputs shows text bottom-anchored inside the line box while the placeholder is centered. After this change, typed text centers alongside the placeholder. Related: facebook#38359, facebook#39145
1 parent 6530092 commit f864b61

4 files changed

Lines changed: 42 additions & 8 deletions

File tree

packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -575,8 +575,8 @@ - (void)setTextAndSelection:(NSInteger)eventCount
575575
}
576576
_comingFromJS = YES;
577577
if (value && ![value isEqualToString:_backedTextInputView.attributedText.string]) {
578-
NSAttributedString *attributedString =
579-
[[NSAttributedString alloc] initWithString:value attributes:_backedTextInputView.defaultTextAttributes];
578+
NSMutableAttributedString *attributedString =
579+
[[NSMutableAttributedString alloc] initWithString:value attributes:_backedTextInputView.defaultTextAttributes];
580580
[self _setAttributedString:attributedString];
581581
[self _updateState];
582582
}
@@ -766,8 +766,10 @@ - (void)_restoreTextSelectionAndIgnoreCaretChange:(BOOL)ignore
766766
[_backedTextInputView setSelectedTextRange:range notifyDelegate:YES];
767767
}
768768

769-
- (void)_setAttributedString:(NSAttributedString *)attributedString
769+
- (void)_setAttributedString:(NSMutableAttributedString *)attributedString
770770
{
771+
RCTApplyBaselineOffset(attributedString);
772+
771773
if ([self _textOf:attributedString equals:_backedTextInputView.attributedText]) {
772774
return;
773775
}

packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ NSMutableDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttri
2828
/*
2929
* Conversions amond `NSAttributedString`, `AttributedString` and `AttributedStringBox`.
3030
*/
31-
NSAttributedString *RCTNSAttributedStringFromAttributedString(
31+
NSMutableAttributedString *RCTNSAttributedStringFromAttributedString(
3232
const facebook::react::AttributedString &attributedString);
3333

34-
NSAttributedString *RCTNSAttributedStringFromAttributedStringBox(
34+
NSMutableAttributedString *RCTNSAttributedStringFromAttributedStringBox(
3535
const facebook::react::AttributedStringBox &attributedStringBox);
3636

3737
facebook::react::AttributedStringBox RCTAttributedStringBoxFromNSAttributedString(

packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
405405
return nsAttributedStringFragment;
406406
}
407407

408-
NSAttributedString *RCTNSAttributedStringFromAttributedString(const AttributedString &attributedString)
408+
NSMutableAttributedString *RCTNSAttributedStringFromAttributedString(const AttributedString &attributedString)
409409
{
410410
static UIImage *placeholderImage;
411411
static dispatch_once_t onceToken;
@@ -428,13 +428,13 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
428428
return nsAttributedString;
429429
}
430430

431-
NSAttributedString *RCTNSAttributedStringFromAttributedStringBox(const AttributedStringBox &attributedStringBox)
431+
NSMutableAttributedString *RCTNSAttributedStringFromAttributedStringBox(const AttributedStringBox &attributedStringBox)
432432
{
433433
switch (attributedStringBox.getMode()) {
434434
case AttributedStringBox::Mode::Value:
435435
return RCTNSAttributedStringFromAttributedString(attributedStringBox.getValue());
436436
case AttributedStringBox::Mode::OpaquePointer:
437-
return (NSAttributedString *)unwrapManagedObject(attributedStringBox.getOpaquePointer());
437+
return [(NSAttributedString *)unwrapManagedObject(attributedStringBox.getOpaquePointer()) mutableCopy];
438438
}
439439
}
440440

packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,38 @@ module.exports = [
11881188
name: 'textStyles',
11891189
render: () => <TextStylesExample />,
11901190
},
1191+
{
1192+
title: 'Single-line lineHeight baseline',
1193+
name: 'lineHeightBaseline',
1194+
description:
1195+
'Regression repro for https://github.com/facebook/react-native/issues/39145. ' +
1196+
'When lineHeight > fontSize on a single-line TextInput, typed text should ' +
1197+
'stay vertically centered. Placeholder centering is already correct; the ' +
1198+
'bug is that typed glyphs anchor to the bottom of the attributed-string ' +
1199+
'line box, appearing visually too low.',
1200+
render: function (): React.Node {
1201+
return (
1202+
<View>
1203+
{[20, 24, 32, 48].map(lineHeight => (
1204+
<WithLabel
1205+
key={lineHeight}
1206+
label={`fontSize: 16, lineHeight: ${lineHeight}`}>
1207+
<ExampleTextInput
1208+
placeholder="placeholder stays centered"
1209+
style={{
1210+
fontSize: 16,
1211+
lineHeight,
1212+
borderColor: '#ccc',
1213+
borderWidth: 1,
1214+
padding: 8,
1215+
}}
1216+
/>
1217+
</WithLabel>
1218+
))}
1219+
</View>
1220+
);
1221+
},
1222+
},
11911223
{
11921224
title: 'showSoftInputOnFocus',
11931225
render: function (): React.Node {

0 commit comments

Comments
 (0)