Summary
On iOS 26, apps using LiquidGlassView (especially with interactive) crash sporadically with memory-corruption signatures during React Native (Fabric) mount/unmount transactions. We root-caused two distinct mechanisms in the library's iOS implementation and validated fixes in production-style stress testing. Happy to send a PR if you agree with the approach.
Environment
@callstack/liquid-glass 0.8.0
- react-native 0.81.4 (New Architecture / Fabric)
- iOS 26.5 (simulator) and iOS 26.6 (device, field crashes)
Crash signatures observed
EXC_BAD_ACCESS in -[_UIPointerInteractionAssistant _assistantForView:] / -[UIView(UIPointerEventsPrivate) _containsView:] while RCTMountingManager performTransaction mounts a child view (use-after-free — the assistant registry enumerates a freed view).
NSInvalidArgumentException: -[__NSArrayM insertSublayer:atIndex:]: unrecognized selector (and OS_xpc_* / CALayerArray variants) during mounting — a freed CALayer's memory reused by an unrelated object, then messaged as a layer.
Both reproduce with rapid navigation churn across screens containing glass views (open/close product-detail screens with glass buttons/badges, tab switches). In our app this crashed roughly 1 in 3 sessions of ~100 navigations.
Root causes
1. Fabric children are mounted directly into UIVisualEffectView.contentView
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
[_view.contentView insertSubview:childComponentView atIndex:index];
}
Fabric assumes it exclusively owns the index space of the container it mounts into. On iOS 26, UIKit's glass/pointer machinery injects private subviews (e.g. _UIPointerEffectPlatterView) into the effect view hierarchy at runtime. Once an injected view lands in contentView, every Fabric index is off by one: subsequent insert/remove transactions operate on the wrong subviews, detaching views the framework still references — producing the freed-layer insertSublayer crashes above.
2. Live glass effect (and its pointer-interaction registration) survives recycle/dealloc
With isInteractive = true, UIGlassEffect registers the view with UIKit's pointer-interaction system. The component view never tears the effect down:
- when Fabric recycles the component view (
prepareForRecycle), the pooled instance keeps its live effect and registration;
- when the view deallocates, the registration is never removed while the view is alive.
The next pointer-interaction registry walk (which UIKit performs during ordinary _addSubview passes) messages the freed view — the _UIPointerInteractionAssistant crash above.
Fix we validated
- Mount Fabric children into a dedicated plain
UIView container inside contentView (autoresized to its bounds). UIKit can then inject anything into the effect hierarchy without disturbing Fabric's index space.
- Add a
resetForRecycle() on LiquidGlassViewImpl that sets effect = nil and clears cached state; call it from prepareForRecycle and dealloc so UIKit unregisters the interactive-glass pointer machinery while the view is still alive.
Patch (applied via patch-package on 0.8.0):
--- a/ios/LiquidGlassView.mm
+++ b/ios/LiquidGlassView.mm
@@
@implementation LiquidGlassView {
LiquidGlassViewImpl * _view;
+ UIView * _childContainer;
BOOL _needsInvalidateLayer;
}
@@
_view = [[LiquidGlassViewImpl alloc] init];
-
+
+ _childContainer = [[UIView alloc] initWithFrame:CGRectZero];
+ _childContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ [_view.contentView addSubview:_childContainer];
+
self.contentView = _view;
}
return self;
}
+- (void)dealloc
+{
+ [_view resetForRecycle];
+}
+
@@
- (void)layoutSubviews {
[super layoutSubviews];
_view.layer.cornerRadius = self.layer.cornerRadius;
_view.layer.cornerCurve = self.layer.cornerCurve;
+ _childContainer.frame = _view.contentView.bounds;
}
+
+- (void)prepareForRecycle
+{
+ [super prepareForRecycle];
+ [_view resetForRecycle];
+}
@@
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
- [_view.contentView insertSubview:childComponentView atIndex:index];
+ [_childContainer insertSubview:childComponentView atIndex:index];
}
--- a/ios/LiquidGlassView.swift
+++ b/ios/LiquidGlassView.swift
@@
+ @objc public func resetForRecycle() {
+ self.effect = nil
+ appliedStyle = nil
+ appliedTintColor = nil
+ appliedInteractive = nil
+ isFirstMount = true
+ effectTintColor = nil
+ interactive = false
+ style = .regular
+ animated = true
+ animationDuration = 0
+ hasAnimationDuration = false
+ }
(Plus an empty resetForRecycle() on the non-iOS-26 fallback class so the ObjC call compiles on older SDK branches.)
Validation
Automated stress loop on iOS 26.5 simulator (Release configuration): repeated product-screen open/close over interactive glass buttons + tab switches, 10 runs x 15 navigation cycles. Before: crashes in ~1/3 of runs (both signatures). After: zero occurrences of either signature.
Related: react/react-native#55489 describes the same _UIPointerInteractionAssistant use-after-free pattern from the pointer-interaction side.
Summary
On iOS 26, apps using
LiquidGlassView(especially withinteractive) crash sporadically with memory-corruption signatures during React Native (Fabric) mount/unmount transactions. We root-caused two distinct mechanisms in the library's iOS implementation and validated fixes in production-style stress testing. Happy to send a PR if you agree with the approach.Environment
@callstack/liquid-glass0.8.0Crash signatures observed
EXC_BAD_ACCESSin-[_UIPointerInteractionAssistant _assistantForView:]/-[UIView(UIPointerEventsPrivate) _containsView:]whileRCTMountingManager performTransactionmounts a child view (use-after-free — the assistant registry enumerates a freed view).NSInvalidArgumentException: -[__NSArrayM insertSublayer:atIndex:]: unrecognized selector(andOS_xpc_*/CALayerArrayvariants) during mounting — a freedCALayer's memory reused by an unrelated object, then messaged as a layer.Both reproduce with rapid navigation churn across screens containing glass views (open/close product-detail screens with glass buttons/badges, tab switches). In our app this crashed roughly 1 in 3 sessions of ~100 navigations.
Root causes
1. Fabric children are mounted directly into
UIVisualEffectView.contentViewFabric assumes it exclusively owns the index space of the container it mounts into. On iOS 26, UIKit's glass/pointer machinery injects private subviews (e.g.
_UIPointerEffectPlatterView) into the effect view hierarchy at runtime. Once an injected view lands incontentView, every Fabric index is off by one: subsequent insert/remove transactions operate on the wrong subviews, detaching views the framework still references — producing the freed-layerinsertSublayercrashes above.2. Live glass effect (and its pointer-interaction registration) survives recycle/dealloc
With
isInteractive = true,UIGlassEffectregisters the view with UIKit's pointer-interaction system. The component view never tears the effect down:prepareForRecycle), the pooled instance keeps its live effect and registration;The next pointer-interaction registry walk (which UIKit performs during ordinary
_addSubviewpasses) messages the freed view — the_UIPointerInteractionAssistantcrash above.Fix we validated
UIViewcontainer insidecontentView(autoresized to its bounds). UIKit can then inject anything into the effect hierarchy without disturbing Fabric's index space.resetForRecycle()onLiquidGlassViewImplthat setseffect = niland clears cached state; call it fromprepareForRecycleanddeallocso UIKit unregisters the interactive-glass pointer machinery while the view is still alive.Patch (applied via patch-package on 0.8.0):
(Plus an empty
resetForRecycle()on the non-iOS-26 fallback class so the ObjC call compiles on older SDK branches.)Validation
Automated stress loop on iOS 26.5 simulator (Release configuration): repeated product-screen open/close over interactive glass buttons + tab switches, 10 runs x 15 navigation cycles. Before: crashes in ~1/3 of runs (both signatures). After: zero occurrences of either signature.
Related: react/react-native#55489 describes the same
_UIPointerInteractionAssistantuse-after-free pattern from the pointer-interaction side.