Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TextInput vertical alignment issue when using lineHeight prop on iOS (Paper - old arch) #37465

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9ae1c50
basic working functionality
fabOnReact May 11, 2023
d382cda
draft solution to fix alignment fontSize higher than lineHeight
fabOnReact May 12, 2023
d23d86e
remove not required changes
fabOnReact May 12, 2023
c40e549
fix regression with border and padding
fabOnReact May 12, 2023
4d1331a
minor changes
fabOnReact May 12, 2023
2c5b47a
Merge branch 'main' into cursor-getting-cut
fabOnReact May 12, 2023
590915b
line height examples
fabOnReact May 12, 2023
20cd89b
re-introduce text-input examples
fabOnReact May 12, 2023
025354f
minor changes to example
fabOnReact May 12, 2023
38f7a8d
fix regression with effectiveFontSizeMultiplier
fabOnReact May 17, 2023
adbac14
fix regression, text getting cut in editing mode when using paddingTop
fabOnReact May 17, 2023
0ff8dc5
rename variable to fragmentViewContainerBounds
fabOnReact May 17, 2023
cdbf426
clean up the diff
fabOnReact May 17, 2023
17c16bd
minor changes
fabOnReact May 17, 2023
a5a5e66
reset Vertical alignment to center
fabOnReact May 17, 2023
d4772ca
remove example
fabOnReact May 17, 2023
070bdb7
Merge branch 'main' into cursor-getting-cut
fabOnReact May 17, 2023
acca018
adding comments
fabOnReact May 17, 2023
85ce3bf
more comments
fabOnReact May 17, 2023
6481272
Merge branch 'main' into cursor-getting-cut
fabOnReact May 18, 2023
3854ec1
Avoid changing contentVerticalAlignment in textRectForBounds
fabOnReact May 18, 2023
5906faf
removing not relevant check on font
fabOnReact May 18, 2023
50deb0c
Remove the borderInsets from the frame causes text to not have left m…
fabOnReact May 19, 2023
548179b
minor changes
fabOnReact May 19, 2023
84dce82
remove example
fabOnReact May 19, 2023
a3e7702
adding comments
fabOnReact May 19, 2023
c4996fc
explanatory comments
fabOnReact May 19, 2023
cb7714f
minor changes
fabOnReact May 19, 2023
30061c0
Merge branch 'main' into cursor-getting-cut
fabOnReact May 19, 2023
f9a415a
minor changes
fabOnReact May 22, 2023
99bded6
update comments
fabOnReact May 25, 2023
4832e89
minor change
fabOnReact May 25, 2023
7a9a38b
changes from code review
fabOnReact May 30, 2023
c04bc43
minor improvement
fabOnReact May 30, 2023
90bfee9
Merge branch 'main' into cursor-getting-cut
fabOnReact May 30, 2023
206951e
adding a line break before comment
fabOnReact Jun 1, 2023
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
16 changes: 13 additions & 3 deletions packages/react-native/Libraries/Text/RCTTextAttributes.m
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,13 @@ - (NSParagraphStyle *)effectiveParagraphStyle

if (!isnan(_lineHeight)) {
CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
isParagraphStyleUsed = YES;

// Text with lineHeight lower than font.lineHeight does not correctly vertically align.
if (lineHeight > self.effectiveFont.lineHeight) {
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
isParagraphStyleUsed = YES;
}
}

if (isParagraphStyleUsed) {
Expand Down Expand Up @@ -172,6 +176,12 @@ - (NSParagraphStyle *)effectiveParagraphStyle
NSParagraphStyle *paragraphStyle = [self effectiveParagraphStyle];
if (paragraphStyle) {
attributes[NSParagraphStyleAttributeName] = paragraphStyle;

// The baseline aligns the text vertically in the line height (_UITextLayoutFragmentView).
if (!isnan(paragraphStyle.maximumLineHeight) && paragraphStyle.maximumLineHeight >= font.lineHeight) {
CGFloat baseLineOffset = (paragraphStyle.maximumLineHeight - font.lineHeight) / 2.0;
attributes[NSBaselineOffsetAttributeName] = @(baseLineOffset);
}
}

// Decoration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign, readonly) BOOL dictationRecognizing;
@property (nonatomic, copy, nullable) NSString *placeholder;
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
@property (nonatomic, assign) CGRect fragmentViewContainerBounds;
@property (nonatomic, assign) UIEdgeInsets textBorderInsets;
@property (nonatomic, assign) UIControlContentVerticalAlignment contentVerticalAlignment;

@property (nonatomic, assign) CGFloat preferredMaxLayoutWidth;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ - (void)paste:(id)sender
[super paste:sender];
}

- (void)setTextBorderInsetsAndFrame:(CGRect)bounds textBorderInsets:(UIEdgeInsets)textBorderInsets
{
_textBorderInsets = textBorderInsets;

// We apply `borderInsets` as the `RCTUITextView` layout offset.
self.frame = UIEdgeInsetsInsetRect(bounds, textBorderInsets);
[self setNeedsLayout];
}

// Turn off scroll animation to fix flaky scrolling.
// This is only necessary for iOS <= 14.
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < 140000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign, readonly) CGFloat zoomScale;
@property (nonatomic, assign, readonly) CGPoint contentOffset;
@property (nonatomic, assign, readonly) UIEdgeInsets contentInset;
@property (nonatomic, assign) CGRect fragmentViewContainerBounds;
@property (nonatomic, assign) UIControlContentVerticalAlignment contentVerticalAlignment;

// This protocol disallows direct access to `selectedTextRange` property because
// unwise usage of it can break the `delegate` behavior. So, we always have to
Expand All @@ -43,6 +45,7 @@ NS_ASSUME_NONNULL_BEGIN
// If the change was a result of user actions (like typing or touches), we MUST notify the delegate.
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange NS_UNAVAILABLE;
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate;
- (void)setTextBorderInsetsAndFrame:(CGRect)bounds textBorderInsets:(UIEdgeInsets)textBorderInsets;

// This protocol disallows direct access to `text` property because
// unwise usage of it can break the `attributeText` behavior.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,27 @@ - (void)uiManagerWillPerformMounting

baseTextInputView.textAttributes = textAttributes;
baseTextInputView.reactBorderInsets = borderInsets;

// The CALayer _UITextLayoutFragmentView does not align correctly
// when adding paragraphStyle.maximumLineHeight to an iOS UITextField (issue #28012).
if (!isnan(textAttributes.lineHeight) && !isnan(textAttributes.effectiveFont.lineHeight)) {
CGFloat effectiveLineHeight = textAttributes.lineHeight * textAttributes.effectiveFontSizeMultiplier;
CGFloat fontLineHeight = textAttributes.effectiveFont.lineHeight;
if (effectiveLineHeight >= fontLineHeight * 2.0) {
CGFloat height = self.layoutMetrics.frame.size.height;
CGFloat width = self.layoutMetrics.frame.size.width;

// Setting contentVerticalAlignment to UIControlContentVerticalAlignmentTop aligns
// _UITextLayoutFragmentView and UITextField on the same ordinate (y coordinate).
baseTextInputView.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;

// Align vertically _UITextLayoutFragmentView in the center of the UITextField (TextInput).
CGFloat padding = (height - effectiveLineHeight) / 2.0;
baseTextInputView.fragmentViewContainerBounds = CGRectMake(0, padding, width, effectiveLineHeight);
}
} else {
baseTextInputView.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
}
baseTextInputView.reactPaddingInsets = paddingInsets;

if (newAttributedText) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, nullable) RCTTextAttributes *textAttributes;
@property (nonatomic, assign) UIEdgeInsets reactPaddingInsets;
@property (nonatomic, assign) UIEdgeInsets reactBorderInsets;
@property (nonatomic, assign) CGRect fragmentViewContainerBounds;
@property (nonatomic, assign) UIControlContentVerticalAlignment contentVerticalAlignment;

@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onSelectionChange;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ - (void)enforceTextAttributesIfNeeded
backedTextInputView.defaultTextAttributes = textAttributes;
}

// Fixes iOS alignment issue caused by adding paragraphStyle.maximumLineHeight to an iOS UITextField
// and vertically aligns _UITextLayoutFragmentView with the parent view UITextField.
- (void)setContentVerticalAlignment:(UIControlContentVerticalAlignment)contentVerticalAlignment
{
_contentVerticalAlignment = contentVerticalAlignment;
self.backedTextInputView.contentVerticalAlignment = contentVerticalAlignment;
}

// Custom bounds used to control vertical position of CALayer _UITextLayoutFragmentView.
// _UITextLayoutFragmentView is the CALayer of UITextField.
- (void)setFragmentViewContainerBounds:(CGRect)fragmentViewContainerBounds
{
_fragmentViewContainerBounds = fragmentViewContainerBounds;
self.backedTextInputView.fragmentViewContainerBounds = fragmentViewContainerBounds;
}

- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets
{
_reactPaddingInsets = reactPaddingInsets;
Expand All @@ -87,9 +103,8 @@ - (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets
- (void)setReactBorderInsets:(UIEdgeInsets)reactBorderInsets
{
_reactBorderInsets = reactBorderInsets;
// We apply `borderInsets` as `backedTextInputView` layout offset.
self.backedTextInputView.frame = UIEdgeInsetsInsetRect(self.bounds, reactBorderInsets);
[self setNeedsLayout];
// Borders are added using insets (UITextField textRectForBound, UITextView setFrame).
[self.backedTextInputView setTextBorderInsetsAndFrame:self.bounds textBorderInsets:reactBorderInsets];
}

- (NSAttributedString *)attributedText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign, readonly) BOOL dictationRecognizing;
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
@property (nonatomic, assign) CGRect fragmentViewContainerBounds;
@property (nonatomic, assign) UIEdgeInsets textBorderInsets;
@property (nonatomic, assign, getter=isEditable) BOOL editable;
@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
[self setNeedsLayout];
}

- (void)setTextBorderInsetsAndFrame:(CGRect)bounds textBorderInsets:(UIEdgeInsets)textBorderInsets
{
_textBorderInsets = textBorderInsets;
[self setNeedsLayout];
}


- (void)setPlaceholder:(NSString *)placeholder
{
[super setPlaceholder:placeholder];
Expand Down Expand Up @@ -170,7 +177,15 @@ - (CGRect)caretRectForPosition:(UITextPosition *)position

- (CGRect)textRectForBounds:(CGRect)bounds
{
return UIEdgeInsetsInsetRect([super textRectForBounds:bounds], _textContainerInset);
// Text is vertically aligned to the center.
CGFloat leftPadding = _textContainerInset.left + _textBorderInsets.left;
CGFloat rightPadding = _textContainerInset.right + _textBorderInsets.right;
UIEdgeInsets borderAndPaddingInsets = UIEdgeInsetsMake(_textContainerInset.top, leftPadding, _textContainerInset.bottom, rightPadding);

// The fragmentViewContainerBounds set the correct y coordinates for
// _UITextLayoutFragmentView to fix an iOS UITextField issue with lineHeight.
CGRect updatedBounds = self.fragmentViewContainerBounds.size.height > 0 ? self.fragmentViewContainerBounds : bounds;
return UIEdgeInsetsInsetRect([super textRectForBounds:updatedBounds], borderAndPaddingInsets);
}

- (CGRect)editingRectForBounds:(CGRect)bounds
Expand Down