From 5ca5e3b6001f7bfcfbc09a441c1956ed17584c41 Mon Sep 17 00:00:00 2001 From: Fabrizio Bertoglio Date: Fri, 2 Feb 2024 09:19:28 -0800 Subject: [PATCH] Fix TextInput vertical alignment issue when using lineHeight prop on iOS without changing Text baseline (Paper - old arch) (#38359) Summary: This PR fixes visual regression introduced with https://github.com/facebook/react-native/pull/37465#issuecomment-1631974927 Adding paragraphStyle.maximumLineHeight to a iOS UITextField displays the text under the UITextField ([ios-screenshot-1][1], [ios-screenshot-2][2], [ios-screenshot-3][3]). The PR implements the logic from RCTTextShadowView [#postprocessAttributedText](https://github.com/facebook/react-native/blob/9ab27e8895d6934e72ebdc601d169578ab9628f1/packages/react-native/Libraries/Text/Text/RCTTextShadowView.m#L165-L167) in RCTBaseTextInpuShadowView [#uiManagerWillPerformMounting](https://github.com/facebook/react-native/blob/4c944540f732c6055d447ecaf37d5c8f3eec1bc4/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m#L130-L192). [1]: https://user-images.githubusercontent.com/24992535/238834159-566f7eef-ea2d-4fd4-a519-099b0a12046c.png "ios-screenshot-1" [2]: https://user-images.githubusercontent.com/24992535/238834184-feb454a9-6504-4832-aec8-989f1d027861.png "ios-screenshot-2" [3]: https://user-images.githubusercontent.com/24992535/238834283-cf572f94-a641-4790-92bf-bbe43afb1443.png "ios-screenshot-3" [4]: https://github.com/Expensify/App/assets/24992535/06726b45-7e35-4003-9fcc-50c8d0dff0f6 [5]: https://github.com/Expensify/App/assets/24992535/d9745d29-8863-4170-bcc3-e78fa7e550d2 fixes https://github.com/facebook/react-native/issues/28012 fixes https://github.com/facebook/react-native/issues/33986 Related https://github.com/facebook/react-native/issues/35741 https://github.com/facebook/react-native/issues/31112 ## Changelog: [IOS] [FIXED] - Fix TextInput vertical alignment issue when using lineHeight prop on iOS without changing Text baseline (Paper - old arch) Pull Request resolved: https://github.com/facebook/react-native/pull/38359 Test Plan: Extensive test included in the PR comments https://github.com/facebook/react-native/pull/37465#issuecomment-1551459879 and https://github.com/Expensify/App/issues/17767#issuecomment-1640032626 Reviewed By: cipolleschi Differential Revision: D52325261 Pulled By: dmytrorykun fbshipit-source-id: d072a598bfaafbbffc41005b1fda1795cf3d8ab9 --- .../TextInput/RCTBaseTextInputShadowView.mm | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm index 1f06b79070aa54..9455bd5734487d 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm @@ -158,6 +158,8 @@ - (void)uiManagerWillPerformMounting [attributedText insertAttributedString:propertyAttributedText atIndex:0]; } + [self postprocessAttributedText:attributedText]; + NSAttributedString *newAttributedText; if (![_previousAttributedText isEqualToAttributedString:attributedText]) { // We have to follow `set prop` pattern: @@ -191,6 +193,52 @@ - (void)uiManagerWillPerformMounting }]; } +- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText +{ + __block CGFloat maximumLineHeight = 0; + + [attributedText enumerateAttribute:NSParagraphStyleAttributeName + inRange:NSMakeRange(0, attributedText.length) + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) { + if (!paragraphStyle) { + return; + } + + maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight); + }]; + + if (maximumLineHeight == 0) { + // `lineHeight` was not specified, nothing to do. + return; + } + + __block CGFloat maximumFontLineHeight = 0; + + [attributedText enumerateAttribute:NSFontAttributeName + inRange:NSMakeRange(0, attributedText.length) + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { + if (!font) { + return; + } + + if (maximumFontLineHeight <= font.lineHeight) { + maximumFontLineHeight = font.lineHeight; + } + }]; + + if (maximumLineHeight < maximumFontLineHeight) { + return; + } + + CGFloat baseLineOffset = maximumLineHeight / 2.0 - maximumFontLineHeight / 2.0; + + [attributedText addAttribute:NSBaselineOffsetAttributeName + value:@(baseLineOffset) + range:NSMakeRange(0, attributedText.length)]; +} + #pragma mark - - (NSAttributedString *)measurableAttributedText