feat(ios): honor textAlignVertical on Paragraph and multiline TextInput#56774
Open
quantizor wants to merge 4 commits into
Open
feat(ios): honor textAlignVertical on Paragraph and multiline TextInput#56774quantizor wants to merge 4 commits into
quantizor 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.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>Test 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.