feat(ios): honor textAlignVertical on Paragraph and multiline TextInput#56774
feat(ios): honor textAlignVertical on Paragraph and multiline TextInput#56774quantizor wants to merge 4 commits into
Conversation
Honor ParagraphAttributes.textAlignVertical when drawing the Fabric
Paragraph view on iOS. Mirrors Android's existing offset computation:
center the line-box stack vertically when 'center' is requested, push
to the bottom when 'bottom' is requested, fall back to top when the
text overflows. Shifts paint origin, highlight rects, hit-test point,
and link/button accessibility rects in lockstep.
Equivalent to CSS Box Alignment Level 3 align-content:
{start,center,end} with safe overflow on a block container.
Extend the textAlignVertical fix from Paragraph to multiline TextInput so a tall fixed-height multiline field can sit centered or bottom-aligned on iOS the same way it already does on Android. Single-line TextInput (UITextField) centers natively, so this only routes when the backed view is the multiline UITextView. Offset is applied via UIScrollView's contentInset.top inside RCTUITextView.layoutSubviews, which is the spec-correct surface: it shifts the default scroll origin without interfering with text layout or user scrolling. When content exceeds bounds the inset falls back to 0 (safe overflow per CSS Box Alignment L3 align-content).
Rename the ObjC property to `textAlignVertical` so the bridge matches the JS prop and the C++ paragraph attribute exactly. The enum type stays `RCTUITextViewTextAlignmentVertical` since it mirrors the C++ enum class name. Skip the contentSize read for the default Auto / Top case so the new code adds zero per-layout work for apps that don't opt in.
Adds RCTUITextView.textAlignVertical to the committed Apple C++ API snapshots so validate_cxx_api_snapshots passes.
|
@CalixTang has imported this pull request. If you are a Meta employee, you can view this in D105240559. |
popsiclelmlm
left a comment
There was a problem hiding this comment.
Thanks for the detailed write-up. One scope issue stood out while checking this against current main: the multiline TextInput half appears to only be wired for Fabric.
The PR adds RCTUITextView.textAlignVertical, but the only native prop bridge I see is in React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm. The legacy/Paper path still goes through Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm and RCTBaseTextInputShadowView.mm, and those do not export/apply textAlignVertical to the backed RCTUITextView. Similarly, Paper <Text> is still driven through RCTTextViewManager / RCTTextShadowView, not this Fabric text layout manager path.
If the intent is Fabric-only, could you scope the changelog/body/test plan to new architecture so users do not expect Paper iOS Text/TextInput parity? If this is meant as a general iOS fix, I think it needs the legacy manager/shadow-view path wired too, or at least a note explaining why Paper is intentionally excluded.
|
Good catch, and yes: this is Fabric-only on purpose, we're not chasing the Paper renderer here. I've scoped the summary, changelog, and test plan to the New Architecture so nobody reads it as Paper parity. The surfaces this targets are the Fabric Paragraph path ( |
Summary:
textAlignVertical(and the CSS-styleverticalAlign) has worked on Android since #34567, but iOS silently ignores it on both<Text>and multiline<TextInput>: every value paints content at the top of the host view. This PR closes both halves of the gap so a fixed-height Text or multiline TextInput withverticalAlign: 'middle'(or'bottom') renders the same on iOS as on Android. Single-line TextInput (UITextField) already centers natively and is intentionally not routed.Scope: New Architecture only. This wires the Fabric iOS renderer (the default since 0.76): the Fabric Paragraph path in
RCTTextLayoutManagerand the Fabric multiline TextInput path inRCTTextInputComponentView/RCTUITextView. The legacy (Paper) iOS<Text>and<TextInput>managers (RCTTextViewManager/RCTBaseTextInputViewManager) are intentionally not wired, so apps still on the old renderer are unaffected. Thanks to @popsiclelmlm for flagging the ambiguity.Computation matches Android's existing
TextLayoutManager.getVerticalOffsetexactly: 0 forauto/top,(boxHeight - textHeight) / 2forcenter,boxHeight - textHeightforbottom, and 0 when content overflows the box. This is the CSS Box Alignment Level 3align-contentalgorithm with safe overflow on a block container.Paragraph (
<Text>)The offset is applied at three sites in
RCTTextLayoutManager.mm:drawAttributedStringshifts the paint origin for the background pass, glyph pass, and highlight rect path.getEventEmitterWithAttributeStringreverse-offsets the incoming touch point so hit-testing still resolves the correct character.getRectWithAttributedStringforward-offsets the enumerated link/button rects so VoiceOver focuses on the right region.Keeping all three in lockstep avoids the class of bug where a centered link "renders below the touch target" or accessibility reads the wrong frame. Measurement is unchanged: alignment shifts the line-box stack inside the box, it doesn't alter intrinsic text size.
Multiline TextInput
The offset is applied via
UIScrollView.contentInset.topinsideRCTUITextView.layoutSubviews. This is the right hook on iOS: it shifts the default scroll origin without interfering with text layout, user scrolling, or RN's existingtextContainerInsetwiring (which is used for padding). When content grows past the bounds, the inset clamps to 0 and normal scrolling takes over.RCTTextInputComponentView.updatePropsreadsparagraphAttributes.textAlignVertical(already parsed byBaseTextInputProps), maps the C++ enum to an ObjC bridge enum (RCTUITextViewTextAlignmentVertical), and pushes it to the backedRCTUITextView. The mapping is also re-applied when the backed view switches from single-line to multiline so the new view picks up the alignment on the same render. The defaultAuto/Topcase short-circuits before readingcontentSizeso apps that don't use the feature pay zero per-layout overhead.No public header changes; ABI is preserved.
Changelog:
[IOS] [ADDED] - Honor
textAlignVertical(and the equivalentverticalAlignstyle) on<Text>and multiline<TextInput>on the New ArchitectureTest Plan:
Tested in a Fabric / Hermes V1 / new-arch Expo app on iPhone 17 simulator (iOS 26.4) at all three values, for both Text and multiline TextInput:
vertical-aligntop(default)middle/centerbottomFor Text: decorations (underline, strike) follow the glyphs in all three positions. Touching anywhere on a centered or bottom-aligned link still emits the correct press event. Overflow (text taller than the box) falls back to top to avoid clipping content.
For multiline TextInput: the caret and placeholder sit at the requested position before any text is typed. Typing extends content naturally; once the content fills past the fixed height, scrolling takes over and the alignment offset clamps to top.
Related
<View justifyContent="center">workaround users have been adopting.