Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@ - (void)_updatePlaceholder
[textAttributes setValue:defaultPlaceholderFont() forKey:NSFontAttributeName];
}

NSParagraphStyle *paragraphStyle = textAttributes[NSParagraphStyleAttributeName];
UIFont *font = textAttributes[NSFontAttributeName];
if (paragraphStyle && font && paragraphStyle.maximumLineHeight > font.lineHeight) {
textAttributes[NSBaselineOffsetAttributeName] = @((paragraphStyle.maximumLineHeight - font.lineHeight) / 2.0);
}

return textAttributes;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,45 @@ - (void)setDefaultTextAttributes:(NSDictionary<NSAttributedStringKey, id> *)defa
[self _updatePlaceholder];
}

// UITextField / UIFieldEditor does not honor NSBaselineOffsetAttributeName when drawing typed
// text, and it draws at the baseline of a paragraphStyle-sized line box — so when
// `lineHeight > font.lineHeight` the glyphs sit at the bottom of that line box. Strip the
// paragraphStyle line-height for the super-class rendering path so UITextField uses its
// default intrinsic line height and its built-in vertical centering positions the glyph in the
// bounds. `_defaultTextAttributes` (local) keeps the unmodified paragraphStyle so the
// placeholder path (`_placeholderTextAttributes`) still sees the real lineHeight.
- (void)setAttributedText:(NSAttributedString *)attributedText
{
if (attributedText.length == 0) {
[super setAttributedText:attributedText];
return;
}
NSMutableAttributedString *mutableStr = [attributedText mutableCopy];
[mutableStr enumerateAttribute:NSParagraphStyleAttributeName
inRange:NSMakeRange(0, mutableStr.length)
options:0
usingBlock:^(NSParagraphStyle *style, NSRange range, __unused BOOL *stop) {
if (!style || style.maximumLineHeight == 0) {
return;
}
UIFont *font = [mutableStr attribute:NSFontAttributeName
atIndex:range.location
effectiveRange:NULL];
if (!font || style.maximumLineHeight <= font.lineHeight) {
return;
}
NSMutableParagraphStyle *stripped = [style mutableCopy];
stripped.minimumLineHeight = 0;
stripped.maximumLineHeight = 0;
[mutableStr addAttribute:NSParagraphStyleAttributeName value:stripped range:range];
// Drop any NSBaselineOffsetAttributeName applied by the Fabric baseline-offset
// helper in the same range: UITextField does not honor it for typed text
// rendering, but a non-zero value still inflates the caret rect.
[mutableStr removeAttribute:NSBaselineOffsetAttributeName range:range];
}];
[super setAttributedText:mutableStr];
}

- (NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
{
return _defaultTextAttributes;
Expand Down Expand Up @@ -169,6 +208,12 @@ - (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts
[textAttributes removeObjectForKey:NSForegroundColorAttributeName];
}

NSParagraphStyle *paragraphStyle = textAttributes[NSParagraphStyleAttributeName];
UIFont *font = textAttributes[NSFontAttributeName];
if (paragraphStyle && font && paragraphStyle.maximumLineHeight > font.lineHeight) {
textAttributes[NSBaselineOffsetAttributeName] = @((paragraphStyle.maximumLineHeight - font.lineHeight) / 2.0);
}

return textAttributes;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,30 @@ - (void)_restoreTextSelectionAndIgnoreCaretChange:(BOOL)ignore

- (void)_setAttributedString:(NSAttributedString *)attributedString
{
// When the user types, UIKit's typingAttributes drop NSParagraphStyleAttributeName, so the
// attributedText round-tripping back through state lacks the paragraph style that
// RCTApplyBaselineOffset needs. Re-seed paragraph style from defaultTextAttributes on ranges
// that are missing it or carry a zero-lineHeight stub, so the helper can compute the offset.
NSMutableAttributedString *mutableString = [attributedString mutableCopy];
NSParagraphStyle *defaultParagraphStyle =
_backedTextInputView.defaultTextAttributes[NSParagraphStyleAttributeName];
if (defaultParagraphStyle && mutableString.length > 0) {
[mutableString
enumerateAttribute:NSParagraphStyleAttributeName
inRange:NSMakeRange(0, mutableString.length)
options:0
usingBlock:^(NSParagraphStyle *style, NSRange range, __unused BOOL *stop) {
if (!style || style.maximumLineHeight == 0) {
[mutableString addAttribute:NSParagraphStyleAttributeName
value:defaultParagraphStyle
range:range];
}
}];
}

RCTApplyBaselineOffset(mutableString);
attributedString = mutableString;

if ([self _textOf:attributedString equals:_backedTextInputView.attributedText]) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,33 @@ module.exports = [
name: 'textStyles',
render: () => <TextStylesExample />,
},
{
title: 'lineHeight baseline',
name: 'lineHeightBaseline',
render: function (): React.Node {
const inputStyle = {
fontSize: 16,
lineHeight: 32,
borderColor: '#ccc',
borderWidth: 1,
padding: 8,
};
return (
<View>
<WithLabel label="single-line">
<ExampleTextInput placeholder="placeholder" style={inputStyle} />
</WithLabel>
<WithLabel label="multi-line">
<ExampleTextInput
multiline
placeholder="placeholder"
style={inputStyle}
/>
</WithLabel>
</View>
);
},
},
{
title: 'showSoftInputOnFocus',
render: function (): React.Node {
Expand Down
Loading