fix(core): insert iOS child views relative to their sibling, not by raw index - #11406
fix(core): insert iOS child views relative to their sibling, not by raw index#11406NathanWalker wants to merge 1 commit into
Conversation
…aw index insertSubview:atIndex: resolves the index against the layer's sublayers, which also hold non-view layers: a CSS gradient background sits at sublayer 0 and each shadowed child adds an outer shadow layer. With any of those ahead of the insertion point, insertChild(view, index) landed the child one position below the sibling it should precede — a view inserted after a ScrollView ended up beneath it and lost its touches, while appending was unaffected. Reproduced with a bare UIView probe on iOS 18.5 and 26.5. IOSHelper.insertSubview() appends past the end and otherwise uses insertSubview:belowSubview: against the subview at the index, which is independent of extra sublayers. View, Page, LiquidGlass and LiquidGlassContainer all go through it.
|
View your CI Pipeline Execution ↗ for commit 43fce35
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
|
@NathanWalker This generated description seems really strange. |
|
@CatchABus thanks for the look; this is not a box-shadow problem (the fix doesn't touch the shadow code) sorry the description buried that. The shadow layer is only one of the extra sublayers that trigger it; the case I actually hit is a CSS linear-gradient background, which core installs as a CAGradientLayer at sublayer 0. The bug is in how children get inserted: every iOS _addViewToNativeVisualTree uses insertSubview:atIndex:, and UIKit resolves that index against the layer's sublayers, not subviews. So with any non-view sublayer ahead of the insertion point, insertChild(view, 1) lands the view at subview 0. Appends are fine, which is why it only shows for inserts in the middle. insertSubview:belowSubview: doesn't have the problem, which is all the PR changes. Your shadow rework would remove one source of extra sublayers, but not the gradient layer (or anything a plugin adds), so I think the insert fix stands on its own. Sample to repro on your end: |
| * background at sublayer 0, an outer shadow layer per shadowed child), so the | ||
| * view lands below the sibling it should precede. | ||
| */ | ||
| static insertSubview(parentNativeView: UIView, childNativeView: UIView, atIndex?: number): void { |
There was a problem hiding this comment.
Maybe we could rename the function to insertNativeSubview to make it distinct for users and plugin maintainers that it doesn't target {N} JS views.
Also, it would be nice to have an android counterpart which could contain the View class implementation by default.
| } | ||
|
|
||
| /** | ||
| * Add `childNativeView` to `parentNativeView` at subview index `atIndex`, or |
There was a problem hiding this comment.
The comment needs shrinking and correction.
|
@NathanWalker Thanks for the detailed response, it makes more sense now! I requested a couple of changes and suggestions. |
On iOS,
insertChild(view, index)can place the child's native view one position lower than asked, beneath the sibling it should sit above. A view inserted after aScrollViewends up underneath it and stops receiving touches while remaining fully visible; a view inserted before an opaque sibling disappears behind it.Root cause: every iOS
_addViewToNativeVisualTreeimplementation usedinsertSubview:atIndex:. UIKit resolves that index against the layer's sublayers, not thesubviewsarray, and core installs non-view sublayers of its own:linear-gradientbackground is aCAGradientLayerat sublayer 0 (background.ios.ts, alsoRootLayout)box-shadowgets an outer shadow layer inserted below its own layerWith any of those ahead of the insertion point, the raw index is off by the number of such layers. Appending (
addChild) is unaffected, which is why this only shows up when a child is inserted in the middle: keyed list updates,@for/v-forprepends, conditional views that mount after their siblings, framework HMR remounts.Fix
New
IOSHelper.insertSubview(parentNativeView, childNativeView, atIndex?)appends when the index is absent or past the end and otherwise inserts withinsertSubview:belowSubview:against the subview currently at that index, which is independent of extra sublayers. All four raw-index sites use it:View._addViewToNativeVisualTreePage._addViewToNativeVisualTreeLiquidGlass._addViewToNativeVisualTreeLiquidGlassContainer._addViewToNativeVisualTreeThe
atIndexcontract is unchanged (a subview index, asProxyViewContainerand_childIndexToNativeChildIndexalready assume), so callers are untouched.