Skip to content

fix(ios): render dim in presenter view when presenter VC is detached#662

Open
kanzelm3 wants to merge 1 commit intolodev09:mainfrom
kanzelm3:fix/detached-presenter-dim
Open

fix(ios): render dim in presenter view when presenter VC is detached#662
kanzelm3 wants to merge 1 commit intolodev09:mainfrom
kanzelm3:fix/detached-presenter-dim

Conversation

@kanzelm3
Copy link
Copy Markdown

Summary

Fixes #661.

On iOS, when a TrueSheet is presented from a UIViewController that is not reachable from the key window's root VC via the parent / presentingViewController chain (i.e. "detached" in UIKit's terms), the sheet content itself renders correctly, but UIKit's UISheetPresentationController dim layer ends up in the wrong UIWindow. iOS surfaces this at runtime with:

[UIKitCore] Presenting view controller <TrueSheetViewController: …> from detached
view controller <...> is not supported, and may result in incorrect safe area
insets and a corrupt root presentation ... Will become a hard exception in a future release.

Two changes, both in ios/TrueSheetView.mm:

  1. findPresentingViewController walks the responder chain to locate the nearest ancestor VC. This correctly finds VCs parked in the view tree by custom navigators (which may use addSubview: rather than modal presentation), where the previous window.rootViewController.presentedViewController chain walk would miss them and fall back to the app root.

  2. New isPresenterDetached: helper walks the parentpresenting chain and only returns NO if it reaches the window's root VC. Detachment is inherited — a VC can have a non-nil presentingViewController but still be detached because that presenter is itself detached, so a single-level check is not sufficient. When the presenter is detached AND dimmed is YES, a fallback dim UIView is added as a subview of the presenter's view so it lives in the correct window below the natively-presented sheet. The dim is removed (animated) in dismissAnimated:, viewControllerWillDismiss, prepareForRecycle, and dealloc so every dismissal path — including swipe-dismiss and Fabric view recycling — cleans up correctly.

No behavior change for the attached-presenter path.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Test Plan

  • Reproduced the bug against the issue's repro (RN <Modal> inside a UIPageViewController-backed tree, TrueSheet inside the Modal). Confirmed the dim now renders correctly covering the Modal content and the sheet animates in/out as expected.
  • Verified swipe-dismiss and programmatic dismiss both clean up the fallback dim in lockstep with the sheet animation — no leaked views or frames.
  • Verified that when the presenter is attached (normal case), isPresenterDetached: returns NO and no fallback dim is added; UIKit's native dim continues to work exactly as before.
  • yarn tidy
  • yarn test ✅ (43 passed)
  • Tested on iOS 26 simulator, RN 0.81, New Architecture enabled.

Screenshots / Videos

Before (dim lands in wrong window, behind the <Modal> content):

[happy to add a short screen recording from the repro app if helpful — let me know]

After (dim correctly covers the presenter's view):

[ditto]

Checklist

  • I tested on iOS
  • I tested on Android (iOS-only change; Android unaffected)
  • I tested on Web (iOS-only change)
  • I updated the documentation (no API change)
  • I added a changelog entry

Notes for reviewer

  • Happy to split this into two commits / PRs if you'd prefer to land the responder-walk separately from the fallback-dim — the former is the smaller, uncontroversial fix and the latter is the more opinionated one.
  • The fallback dim currently always shows full opacity when the sheet is presented (matching dimmedDetentIndex: 0 semantics, which is the common case). If you'd like it to respect dimmedDetentIndex > 0 and fade with detent changes, happy to follow up — want to keep this PR focused for easier review.
  • Dim color is rgba(0, 0, 0, 0.4) — close to UIKit's default. Open to changing if there's a preferred value.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

@kanzelm3 is attempting to deploy a commit to the Dinky Team on Vercel.

A member of the Team first needs to authorize it.

@kanzelm3 kanzelm3 force-pushed the fix/detached-presenter-dim branch from 71f3a1c to 8b6ff91 Compare April 23, 2026 09:23
When TrueSheet is presented from a UIViewController that is not reachable
from the key window's root VC via the parent/presentingViewController
chain, UIKit's built-in UISheetPresentationController dim layer lands in
the wrong UIWindow — the sheet content renders correctly but the dim
appears in a fallback container instead of behind the sheet.

iOS surfaces this at runtime as:
  "Presenting view controller ... from detached view controller ... is
   not supported, and may result in incorrect safe area insets and a
   corrupt root presentation."

Detachment is inherited up the chain: a VC can have a non-nil
presentingViewController but still be detached if that presenter is
itself detached. A single-level check is not sufficient.

Two changes:

1. findPresentingViewController walks superview.nextResponder to locate
   the nearest ancestor VC. This correctly finds VCs parked in the view
   tree by custom navigators (which may use addSubview: rather than
   modal presentation), where the previous window.rootViewController
   presentedViewController chain walk would miss them.

2. Added isPresenterDetached: which walks parent/presenting chain until
   it reaches the window's root VC. When the presenter is detached AND
   dimmed is YES, we add a fallback dim UIView as a subview of the
   presenter's view so it lives in the correct window below the
   natively-presented sheet. The dim is removed (animated) in
   dismissAnimated:, viewControllerWillDismiss, prepareForRecycle, and
   dealloc so every dismissal path — including swipe-dismiss and Fabric
   view recycling — cleans up correctly.

No behavior change for the attached-presenter path.

Closes lodev09#661
@kanzelm3 kanzelm3 force-pushed the fix/detached-presenter-dim branch from 8b6ff91 to 501966f Compare April 23, 2026 10:34
@karimcambridge
Copy link
Copy Markdown

karimcambridge commented Apr 24, 2026

I think I’m having a similar issue! I'm using true-sheet with true-tabs as the footer on my main home screen, then i navigate other screens in the main react-navigation (latest v7) tabs, then when i try to navigate back to home, it says [Error: Uncaught (in promise, id: 0): "Error: No presenting view controller found"] I've been fighting for a workaround even using the imperative methods. I think this PR might fix that issue.

@lodev09
Copy link
Copy Markdown
Owner

lodev09 commented Apr 24, 2026

I'm not sure about this. I'm trying to avoid functionalities that aren't supported natively, unless it's a real bug/issue. I believe you could also just render a custom backdrop yourself and even integrate reanimated to control the dim.

@kanzelm3
Copy link
Copy Markdown
Author

I'm not sure about this. I'm trying to avoid functionalities that aren't supported natively, unless it's a real bug/issue. I believe you could also just render a custom backdrop yourself and even integrate reanimated to control the dim.

I understand that, but you implemented a workaround for the sheet presenting below a modal... so not sure why you wouldn't also ensure that dimmed works in that case too. I banged my head against this for hours and realized it was a bug in the library.

@lodev09
Copy link
Copy Markdown
Owner

lodev09 commented Apr 27, 2026

I'm not sure about this. I'm trying to avoid functionalities that aren't supported natively, unless it's a real bug/issue. I believe you could also just render a custom backdrop yourself and even integrate reanimated to control the dim.

I understand that, but you implemented a workaround for the sheet presenting below a modal... so not sure why you wouldn't also ensure that dimmed works in that case too. I banged my head against this for hours and realized it was a bug in the library.

Yeah plenty of hacks on android just to make it consistent with IOS. What I'm avoiding is hacking IOS since things don't usually work correctly. Android is not very opinionated so you can do whatever you want.

On your case, if you could make it work like the normal dim behavior then I'm good with it. Otherwise, I'll just treat it as limitation (which this library has plenty of).

@lodev09
Copy link
Copy Markdown
Owner

lodev09 commented Apr 27, 2026

Did you try using custom dim backdrop yourself in JS side?

@kanzelm3
Copy link
Copy Markdown
Author

Did you try using custom dim backdrop yourself in JS side?

I did but ran into timing issues with the fade in/out, will give it another go using the reanimated integration.

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 dim renders in wrong UIWindow when sheet is presented from a detached UIViewController

3 participants