Skip to content

Commit 04e01ef

Browse files
Compute the paragraph drawing frame at mount time, not in layoutSubviews
`RCTParagraphComponentView` derives the text view's frame and drawing frame in `layoutSubviews`, requested with `setNeedsLayout` from `updateState` and `updateLayoutMetrics`. When a mount runs inside Core Animation's display phase — which is where `AppleEventBeat` processes a synchronous event requested during layout — this transaction's layout pass has already happened, so the text view displays with the new attributed string but the previous drawing frame: the text is cut off at the old width. The queued `layoutSubviews` then updates the drawing frame in the next transaction, but that property does not invalidate the display, so the clipped drawing stays until something else redraws the view. Compute the frames in `finalizeUpdates:` instead. The mounting manager calls it once per view after all of a mutation's `update*` calls, so it coalesces state and layout-metrics changes the same way the layout pass did, without depending on a layout pass that may already be over. One computation per mount, as before, minus the layout pass.
1 parent f03f6c2 commit 04e01ef

1 file changed

Lines changed: 12 additions & 6 deletions

File tree

‎packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &
137137
{
138138
_textView.state = std::static_pointer_cast<const ParagraphShadowNode::ConcreteState>(state);
139139
[_textView setNeedsDisplay];
140-
[self setNeedsLayout];
141140

142141
// If the attributed string has changed, we need to notify the accessibility system that something changed,
143142
// otherwise it may hold on to stale values (this happens most often when an element is updated async)
@@ -158,9 +157,18 @@ - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
158157
// re-applying individual sub-values which weren't changed.
159158
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics];
160159
_textView.layoutMetrics = _layoutMetrics;
161-
_textLayoutFrame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
162160
[_textView setNeedsDisplay];
163-
[self setNeedsLayout];
161+
}
162+
163+
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
164+
{
165+
[super finalizeUpdates:updateMask];
166+
// Computed here rather than in `layoutSubviews`: a mount can run inside Core
167+
// Animation's display phase, after this transaction's layout pass, and the
168+
// text view would then draw with the previous frame.
169+
if (updateMask & (RNComponentViewUpdateMaskState | RNComponentViewUpdateMaskLayoutMetrics)) {
170+
[self _updateTextViewFrame];
171+
}
164172
}
165173

166174
- (void)prepareForRecycle
@@ -170,10 +178,8 @@ - (void)prepareForRecycle
170178
_accessibilityProvider = nil;
171179
}
172180

173-
- (void)layoutSubviews
181+
- (void)_updateTextViewFrame
174182
{
175-
[super layoutSubviews];
176-
177183
CGRect textViewFrame = self.bounds;
178184
CGRect drawingFrame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
179185

0 commit comments

Comments
 (0)