fix(ios): ensure contentView forwards touches after glass effect setup#38
Merged
satya164 merged 1 commit intocallstack:mainfrom Mar 27, 2026
Merged
Conversation
When UIGlassEffect is applied to a UIVisualEffectView, the internal contentView (_UIVisualEffectContentView) can be reconfigured with userInteractionEnabled = false if no subviews are present at the time the effect is set. In React Native's Fabric renderer, child component views are mounted into contentView via mountChildComponentView: — but this can happen *after* layoutSubviews triggers setupView() and applies the glass effect. The result is that contentView's touch handling is locked in a "no children" state, and Pressable/Touchable children never receive touch events for the lifetime of that view. This is a race condition that depends on main thread scheduling between Fabric's mount pass and UIKit's layout pass. It manifests as an intermittent, session-level bug: either all LiquidGlassView instances in a Fabric commit get working touch, or none of them do. The fix: explicitly set contentView.isUserInteractionEnabled = true after every effect application, guaranteeing touches always flow through to child views regardless of mount ordering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Would love this to get merged is a pretty big bug for us :) |
okwasniewski
approved these changes
Mar 25, 2026
atlj
approved these changes
Mar 27, 2026
atlj
left a comment
There was a problem hiding this comment.
I couldn't reproduce the issue with the given snippet, but I'm assuming you're seeing the issue on a real-world app with actual CPU load.
After seeing the docs for isUserInteractionEnabled, I'm thinking this is good to merge since we don't have any actual reason for this to be false.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When
LiquidGlassViewis used in React Native (Fabric renderer) with interactive child components likePressable, touches intermittently fail to reach those children. The bug is session-level: either allLiquidGlassViewinstances work for the entire app session, or none of them do. A cold restart may or may not fix it.Root Cause
There is a race condition between Fabric's component mount pass and UIKit's layout pass.
LiquidGlassViewImpl.setupView()is called fromlayoutSubviews()and applies theUIGlassEffectviaself.effect = glassEffect. WhenUIGlassEffectis applied, UIKit internally reconfigures thecontentView(_UIVisualEffectContentView). If no subviews are present incontentViewat that moment, thecontentViewcan end up withuserInteractionEnabled == false.In Fabric, child component views are mounted into
contentViewviamountChildComponentView:index:— but this can happen afterlayoutSubviewshas already triggeredsetupView(). The ordering depends on main thread scheduling and varies per app launch.Once
setupView()runs and thelayoutSubviewsguard (if self.effect != nil { return }) prevents it from re-running, thecontentViewtouch state is locked for the lifetime of that view.Timeline — broken session:
layoutSubviews→setupView()→self.effect = glassEffect→contentView.isUserInteractionEnabledset tofalse(no subviews yet)Pressablechildren intocontentViewTimeline — working session:
Pressablechildren intocontentViewlayoutSubviews→setupView()→self.effect = glassEffect→contentViewtouch handling set up correctly (subviews present)Fix
Explicitly set
self.contentView.isUserInteractionEnabled = trueafter every effect application insetupView(). This guarantees touches always flow through to child views regardless of mount ordering.Environment
Reproduction
Cold-restart the app multiple times. On some launches, the
Pressablewill not respond to taps. Once it fails, it stays broken for the entire session. Once it works, it stays working.