Skip to content

Commit dce50dd

Browse files
committed
fix(ios): re-lay out a misplaced first text fragment in RCTUITextView on iOS 27
On iOS 27, TextKit 2 can leave the first layout fragment of a UITextView pushed down by its own height after an edit that replaces text inside the first paragraph while later paragraphs exist, e.g. Korean syllable recomposition. RCTUITextView is sized with frames, so nothing lays the fragment out again and the whole text stays shifted until the next edit (FB25281535). After each text change, check the first layout fragment and, when it is misplaced, invalidate and re-lay out only that fragment's range. Fixes #59386
1 parent 7fc911a commit dce50dd

1 file changed

Lines changed: 29 additions & 0 deletions

File tree

‎packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,35 @@ - (void)textDidChange
152152
{
153153
_textWasPasted = NO;
154154
[self _invalidatePlaceholderVisibility];
155+
[self _relayoutFirstTextLayoutFragmentIfMisplaced];
156+
}
157+
158+
// On iOS 27, TextKit 2 can leave the first layout fragment pushed down by its own height (origin.y == height) after
159+
// an edit that replaces text inside the first paragraph while later paragraphs exist, e.g. Korean syllable
160+
// recomposition in a non-scrolling text view. UIKit hides this when Auto Layout re-measures the view, but views
161+
// here are sized with frames, so the whole text stays shifted until the next edit (FB25281535).
162+
// Re-laying out just that fragment fixes it. Nothing is done while the fragment is in place.
163+
- (void)_relayoutFirstTextLayoutFragmentIfMisplaced
164+
{
165+
if (@available(iOS 16.0, tvOS 16.0, *)) {
166+
NSTextLayoutManager *textLayoutManager = self.textLayoutManager;
167+
if (textLayoutManager == nil) {
168+
return;
169+
}
170+
__block NSTextLayoutFragment *firstFragment = nil;
171+
[textLayoutManager enumerateTextLayoutFragmentsFromLocation:textLayoutManager.documentRange.location
172+
options:NSTextLayoutFragmentEnumerationOptionsNone
173+
usingBlock:^BOOL(NSTextLayoutFragment *fragment) {
174+
firstFragment = fragment;
175+
return NO;
176+
}];
177+
if (firstFragment == nil || firstFragment.layoutFragmentFrame.origin.y <= 0) {
178+
return;
179+
}
180+
NSTextRange *range = firstFragment.rangeInElement;
181+
[textLayoutManager invalidateLayoutForRange:range];
182+
[textLayoutManager ensureLayoutForRange:range];
183+
}
155184
}
156185

157186
- (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts

0 commit comments

Comments
 (0)