Skip to content

[iOS][Fabric] ScrollView content permanently invisible: inner RCTEnhancedScrollView left at CGRectZero when the autoresizing delta is missed #57520

Description

@davidecallegaro

Description

On the 新建 Architecture, RCTScrollViewComponentView sizes its inner RCTEnhancedScrollView only via an autoresizing mask: the scroll view is created with initWithFrame:self.bounds — which is CGRectZero at component-view creation — and relies on UIViewAutoresizingFlexibleWidth | FlexibleHeight deltas to grow when the shell's frame is later applied (source).

Autoresizing only propagates deltas at the moment the parent's frame changes. Under heavy mount/teardown churn (reliably reproducible on a thermally-throttled device, where JS-driven mount choreography stretches from milliseconds to seconds), that delta moment can be missed. The inner UIScrollView is then left at CGRectZero permanently — there is no code path that ever re-establishes the invariant:

  • the shell has its correct Yoga frame (onLayout fires normally),
  • onContentSizeChange fires normally (state events come from the ShadowTree, not the native view),
  • the content subtree is fully mounted and correctly laid out inside the scroll view,
  • but everything is clipped into a zero-size viewport → content is invisible while the app believes everything is fine.

Captured native hierarchy of a live occurrence (Xcode view debugger, real device):

RCTScrollViewComponentView   frame = (0, 429, 430, 503)   ← correct Yoga frame
  RCTEnhancedScrollView      frame = (0, 0, 0, 0)         ← never sized
    UIView (container)       frame = (0, 0, 430, 1024)    ← matches contentSize
      … full content tree, all frames correct, invisible …

To identify the writer we swizzled -[RCTEnhancedScrollView setFrame:] to veto+stack-trace any zero-size write: it never fires. Nothing writes a zero frame — the view is born at zero and the grow-delta is missed. This distinguishes the bug from external interference and points at the reliance on autoresizing itself.

Frequency in the wild: on a hot iPhone 15 Pro Max (thermal state "serious") this hit nearly every mount of screens whose ScrollView mounts one frame after the screen (skeleton→content swap), across multiple independent screens. It is also consistent with long-standing "ScrollView renders invisible after logout/onboarding transition" reports that apps (including ours) have historically dodged with full app reloads.

Related (but distinct, both recycling-related — recycling is off by default and was disabled in our repro): #55090, #34165.

Steps to reproduce

The failure is a race, so there is no minimal synchronous repro; the deterministic statement of the bug is:

  1. RCTScrollViewComponentView is created (frame CGRectZero) → _scrollView created at zero with autoresizing W+H.
  2. If the shell's frame assignment lands in any state where autoresizing cannot propagate the delta to _scrollView, the scroll view stays 0×0.
  3. No subsequent code (updateLayoutMetrics, state updates, layoutSubviews) ever corrects it → permanent invisible content.

Practical reproduction (how we hit it ~every mount): iPhone at thermal state serious (charge + load), a screen that mounts its ScrollView one requestAnimationFrame after the screen mounts, navigate to the screen repeatedly. Verified on RN 0.83.6 / iOS 26.5 / 新建 Architecture / Hermes; the relevant code is unchanged on main.

Expected / actual

  • Expected: the inner scroll view always tracks the shell's bounds.
  • Actual: it can be left at CGRectZero forever; all content invisible; no error anywhere (JS, ShadowTree, or UIKit).

Fix

PR attached: enforce the invariant explicitly — re-glue _scrollView to the shell's bounds in updateLayoutMetrics and layoutSubviews (bounds.size + center rather than frame, so the RTL transform is respected and bounds.origin = contentOffset is preserved). Verified on-device: the desync still occurs under the same conditions and now heals within one layout pass; the invisible-content states no longer reproduce.

Metadata

Metadata

Assignees

No one assigned

    标签

    Needs: Author FeedbackNeeds: ReproThis issue could be improved with a clear list of steps to reproduce the issue.StaleThere has been a lack of activity on this issue and it may be closed soon.

    Type

    No type

    项目

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions