[iOS] Ensure RCTScrollViewComponentView scroll view always matches its bounds#57521
[iOS] Ensure RCTScrollViewComponentView scroll view always matches its bounds#57521davidecallegaro wants to merge 1 commit into
Conversation
The inner RCTEnhancedScrollView is created at CGRectZero and sized only by its autoresizing mask. Autoresizing propagates deltas at the moment the parent frame changes; if that moment is missed (heavy mount / teardown churn, reproducible on thermally-throttled devices), the scroll view stays zero-sized forever and fully-mounted, correctly-laid-out content is clipped into invisibility while ShadowTree layout events keep firing. Enforce the invariant explicitly in updateLayoutMetrics and layoutSubviews, preserving contentOffset (bounds.origin) and the RTL transform (bounds.size + center rather than frame).
|
Hi @davidecallegaro! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
Summary
Fixes the "ScrollView content laid out but never drawn" failure on Fabric iOS:
RCTScrollViewComponentViewsizes its innerRCTEnhancedScrollViewonly via an autoresizing mask, and the scroll view is created atCGRectZero(initWithFrame:self.boundsat component-view creation time). Autoresizing propagates deltas only at the instant the parent's frame changes — if that moment is missed (observed reliably under heavy mount/teardown churn on thermally-throttled devices), the inner scroll view remains 0×0 forever: the shell carries its correct Yoga frame,onLayout/onContentSizeChangefire normally, the content subtree is fully mounted and laid out — and everything is clipped into a zero-size viewport.This change enforces the invariant explicitly instead of relying on autoresizing alone:
_ensureScrollViewMatchesBoundsre-glues the scroll view to the shell's bounds inupdateLayoutMetrics:andlayoutSubviews. It adjustsbounds.size+center(notframe) so the RTLscale(-1, 1)transform is respected, and preservesbounds.origin, which forUIScrollViewis thecontentOffset. No-op when everything is healthy.Diagnosis evidence (native view-hierarchy capture of a live occurrence, plus a
setFrame:veto-swizzle proving nothing ever writes the zero frame — the view is born at zero and the grow-delta is missed) is in the linked issue.Fixes #57520
Changelog:[iOS] [Fixed] - Ensure ScrollView's native scroll view always matches its component view bounds (fixes permanently-invisible ScrollView content when the autoresizing delta is missed)
Test Plan
serious, screen whose ScrollView mounts one frame after the screen, repeated navigation) the desync still occurs and is now corrected within one layout pass — logged heals confirm the code path executes; the invisible-content state no longer reproduces (previously ~every mount).