From b69e28174e1d1fec47fcb7fe1100249474530e9a Mon Sep 17 00:00:00 2001 From: Jason Zheng Date: Thu, 5 Nov 2026 16:16:31 -0800 Subject: [PATCH] Draw text with unbounded height on iOS (#59793) Summary: Fixes an issue where text wrapping behavior on iOS differs from Android and web when the text does not fit in its parent container. ### Root Cause On iOS, React Native lays out text during measurement and drawing. `RCTTextLayoutManager::measureNSAttributedString()` uses an unbounded height during measurement to position where text and inline views should be. However, during drawing, the actual frame size is used. When TextKit reaches a point where the next line of text doesn't fit inside the container's height, it stops wrapping and puts everything on the current line. This leads to different wrapping behavior compared to Android and web, especially when inline views are included in text. ### This PR Modify RCTTextLayoutManager so that drawing also uses unbounded height, matching the existing measurement code. This means TextKit never tries to alter wrapping behavior. Changelog: [iOS][Fixed] - Text taller than a fixed-height `` now wraps normally and is clipped at the bottom edge, matching Android and web Reviewed By: vzaidman Differential Revision: D127931949 --- .../textlayoutmanager/RCTTextLayoutManager.mm | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index 3e93b60e64b..994f46d2dd4 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -39,6 +39,18 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi } } +// Text is laid out with an unbounded height, as during measurement, so that lines and attachments end up where +// measurement placed them. A container clamped to the frame height makes TextKit squeeze the remaining content into +// the last line that fits. The paragraph view clips the drawing to its bounds. +static CGSize RCTTextLayoutSizeForFrameSize(CGSize frameSize, const ParagraphAttributes ¶graphAttributes) +{ + if (paragraphAttributes.adjustsFontSizeToFit) { + // Font scaling shrinks the text until it fits the frame height. + return frameSize; + } + return CGSize{frameSize.width, CGFLOAT_MAX}; +} + - (TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes layoutContext:(TextLayoutContext)layoutContext @@ -136,7 +148,7 @@ - (void)drawAttributedString:(const AttributedString &)attributedString NSTextStorage *textStorage = [self _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] paragraphAttributes:paragraphAttributes - size:frame.size]; + size:RCTTextLayoutSizeForFrameSize(frame.size, paragraphAttributes)]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -475,7 +487,7 @@ - (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttribute NSTextStorage *textStorage = [self _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] paragraphAttributes:paragraphAttributes - size:frame.size]; + size:RCTTextLayoutSizeForFrameSize(frame.size, paragraphAttributes)]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject; @@ -506,7 +518,7 @@ - (void)getRectWithAttributedString:(const AttributedString &)attributedString NSTextStorage *textStorage = [self _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] paragraphAttributes:paragraphAttributes - size:frame.size]; + size:RCTTextLayoutSizeForFrameSize(frame.size, paragraphAttributes)]; NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; NSTextContainer *textContainer = layoutManager.textContainers.firstObject;