Skip to content

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

Closed as not planned

Description

@davidecallegaro

Description

On the New 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 / New 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.

Activity

  1. react-native-bot commented on Jul 11, 2026

    @react-native-bot
    Collaborator

    Warning

    Missing reproducer: We could not detect a reproducible example in your issue report. Reproducers are mandatory and we can accept only one of those as a valid reproducer:


    You can read more about about it on our website: How to report a bug.

  2. react-native-bot commented on Aug 4, 2026

    @react-native-bot
    Collaborator

    This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days.

  3. added
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    and removed
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on Aug 4, 2026
  4. react-native-bot commented on Aug 29, 2026

    @react-native-bot
    Collaborator

    This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days.

  5. added
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on Aug 29, 2026
  6. react-native-bot commented on Sep 5, 2026

    @react-native-bot
    Collaborator

    This issue was closed because the author hasn't provided the requested feedback after 7 days.

  7. removed
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: Author FeedbackNeeds: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions