Skip to content

fix(ios): defer SwiftUI host setup until reactViewController is ready#1088

Open
IsaacIsrael wants to merge 1 commit into
callstack:masterfrom
IsaacIsrael:fix/ios-blank-page-when-react-vc-not-ready
Open

fix(ios): defer SwiftUI host setup until reactViewController is ready#1088
IsaacIsrael wants to merge 1 commit into
callstack:masterfrom
IsaacIsrael:fix/ios-blank-page-when-react-vc-not-ready

Conversation

@IsaacIsrael

@IsaacIsrael IsaacIsrael commented Jun 24, 2026

Copy link
Copy Markdown

Fixes #1089

Summary

On the New Architecture iOS path, PagerView can render a permanently blank page, intermittently, with no content visible even though children are mounted with valid frames.

Root cause

In PagerViewProvider.setupView(), self.hostingController is assigned before verifying that reactViewController() is non-nil:

private func setupView() {
  if self.hostingController != nil {
    return
  }

  self.hostingController = UIHostingController(   // assigned unconditionally
    rootView: PagerView(props: props, delegate: delegate),
    ignoreSafeArea: true
  )
  if let hostingController, let parentViewController = reactViewController() {
    // addChild / addSubview / pinEdges happen only here
  }
}

setupView() is called from didMoveToWindow(). On the first pass reactViewController() can still be nil (the pager is in a window but its screen/view-controller isn't attached yet). When that happens:

  1. The if let … reactViewController() block is skipped, so the SwiftUI hosting view is never added as a subview and never pinned — its frame stays .zero, so the page is blank.
  2. self.hostingController is now non-nil, so on the next didMoveToWindow() (when the view controller is available) the if self.hostingController != nil { return } guard short-circuits and setup never retries.

The result is a blank pager for the lifetime of the view. Because it depends on whether reactViewController() happens to be ready on the first pass, it reproduces intermittently (more often when the pager is mounted during a navigation transition / inside a freshly-pushed screen).

I confirmed this on a device by logging frames: provider.bounds was correct (e.g. {390, 754}) while hostingController.view.frame remained {0, 0, 0, 0}, and the failing pass logged reactViewController() == nil.

Implementation

  • Guard before assigning. setupView() now bails out early (leaving hostingController nil) when reactViewController() is nil, instead of half-initializing. The hosting controller is only created/assigned once it can actually be attached.
  • Retry on layout. Added a layoutSubviews() override that calls setupView() while in-window, so attachment happens as soon as the view-controller hierarchy is ready. The existing hostingController != nil guard keeps subsequent calls a cheap no-op.

This is a minimal, behavior-preserving change for the happy path (when reactViewController() is available on the first pass, behavior is identical).

Test Plan

What's required for testing (prerequisites)?

iOS, New Architecture enabled. A PagerView mounted inside a screen that is pushed/attached (e.g. behind a navigation transition or tab) so that reactViewController() is nil on the first didMoveToWindow.

What are the steps to reproduce (after prerequisites)?

  1. Navigate to a screen that renders a PagerView as part of its initial mount.
  2. Before the fix: the page area is blank (intermittently); switching pages does not help.
  3. After the fix: content renders consistently; the host attaches once the view controller becomes available.

Compatibility

OS Implemented
iOS
Android

Checklist

  • I have tested this on a device and a simulator
  • I added the documentation in README.md
  • I updated the typed files (TS and Flow)

Made with Cursor

setupView() assigned self.hostingController before verifying that
reactViewController() was non-nil. On the first didMoveToWindow pass the
React view controller can still be nil, so the `if let ... reactViewController()`
block was skipped and the SwiftUI hosting view was never added or pinned (its
frame stayed .zero, rendering the page blank). Because self.hostingController
was already set, the early-return guard then blocked every later retry, so the
pager stayed blank for the lifetime of the view. Timing-dependent, so it
reproduced intermittently.

Guard on reactViewController() before creating/assigning the hosting
controller, and retry setupView() from layoutSubviews() so attachment happens
as soon as the view controller hierarchy is ready.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] New Arch: PagerView renders a blank page when reactViewController() is nil on first didMoveToWindow

1 participant